Statusphere running on a slice 馃崟
at main 5.0 kB view raw
1import { render } from "preact-render-to-string"; 2import { App } from "./components/App.tsx"; 3import { StatusTimeline } from "./components/HomePage.tsx"; 4import { PORT, sessionStore, oauthSessions, atprotoClient } from "./config.ts"; 5import { AuthenticatedUser } from "./types.ts"; 6import { fetchStatusesWithAuthors } from "./api.ts"; 7import { getUserTimezone } from "./utils.ts"; 8 9async function handler(req: Request): Promise<Response> { 10 const url = new URL(req.url); 11 const pathname = url.pathname; 12 13 // Get current user session 14 const currentUser = await sessionStore.getCurrentUser(req); 15 16 // Routing 17 if (pathname === "/") { 18 return await handleHome(req, currentUser); 19 } 20 21 if (pathname === "/login") { 22 return handleLogin(req, currentUser); 23 } 24 25 if (pathname === "/oauth/authorize" && req.method === "POST") { 26 return await handleOAuthAuthorize(req); 27 } 28 29 if (pathname === "/oauth/callback") { 30 return await handleOAuthCallback(req); 31 } 32 33 if (pathname === "/logout" && req.method === "POST") { 34 return await handleLogout(req); 35 } 36 37 if (pathname === "/status" && req.method === "POST") { 38 return await handleSetStatus(req, currentUser); 39 } 40 41 return new Response("Not Found", { status: 404 }); 42} 43 44async function handleHome( 45 req: Request, 46 currentUser: AuthenticatedUser 47): Promise<Response> { 48 const statuses = await fetchStatusesWithAuthors(); 49 const userTimezone = getUserTimezone(req); 50 const html = render(App({ currentUser, statuses, userTimezone })); 51 52 return new Response(`<!DOCTYPE html>${html}`, { 53 headers: { "Content-Type": "text/html" }, 54 }); 55} 56 57function handleLogin(req: Request, currentUser: AuthenticatedUser): Response { 58 if (currentUser.isAuthenticated) { 59 return Response.redirect(new URL("/", req.url), 302); 60 } 61 62 const html = render(App({ currentUser, page: "login" })); 63 64 return new Response(`<!DOCTYPE html>${html}`, { 65 headers: { "Content-Type": "text/html" }, 66 }); 67} 68 69async function handleOAuthAuthorize(req: Request): Promise<Response> { 70 await atprotoClient.oauth?.logout(); 71 72 const formData = await req.formData(); 73 const loginHint = formData.get("loginHint") as string; 74 75 const authResult = await atprotoClient.oauth!.authorize({ loginHint }); 76 77 return Response.redirect(authResult.authorizationUrl, 302); 78} 79 80async function handleOAuthCallback(req: Request): Promise<Response> { 81 const url = new URL(req.url); 82 const code = url.searchParams.get("code"); 83 const state = url.searchParams.get("state"); 84 85 if (!code || !state) { 86 return new Response("Missing OAuth parameters", { status: 400 }); 87 } 88 89 await atprotoClient.oauth!.handleCallback({ code, state }); 90 91 const sessionId = await oauthSessions.createOAuthSession(); 92 93 if (!sessionId) { 94 return new Response("Failed to create session", { status: 500 }); 95 } 96 97 const sessionCookie = sessionStore.createSessionCookie(sessionId); 98 99 return new Response(null, { 100 status: 302, 101 headers: { 102 Location: new URL("/", req.url).toString(), 103 "Set-Cookie": sessionCookie, 104 }, 105 }); 106} 107 108async function handleLogout(req: Request): Promise<Response> { 109 const session = await sessionStore.getSessionFromRequest(req); 110 111 if (session) { 112 await oauthSessions.logout(session.sessionId); 113 } 114 115 const clearCookie = sessionStore.createLogoutCookie(); 116 117 return new Response(null, { 118 status: 302, 119 headers: { 120 Location: new URL("/login", req.url).toString(), 121 "Set-Cookie": clearCookie, 122 }, 123 }); 124} 125 126async function handleSetStatus( 127 req: Request, 128 currentUser: AuthenticatedUser 129): Promise<Response> { 130 if (!currentUser.isAuthenticated) { 131 // For HTMX requests, send redirect header 132 const isHtmxRequest = req.headers.get("HX-Request") === "true"; 133 if (isHtmxRequest) { 134 return new Response(null, { 135 status: 200, 136 headers: { 137 "HX-Redirect": "/login", 138 }, 139 }); 140 } 141 return Response.redirect(new URL("/login", req.url), 302); 142 } 143 144 const isHtmxRequest = req.headers.get("HX-Request") === "true"; 145 146 const formData = await req.formData(); 147 const status = formData.get("status") as string; 148 149 try { 150 await atprotoClient.xyz.statusphere.status.createRecord({ 151 status, 152 createdAt: new Date().toISOString(), 153 }); 154 } catch (error) { 155 console.error("Error setting status:", error); 156 return new Response("Error setting status", { status: 500 }); 157 } 158 159 if (isHtmxRequest) { 160 // Return updated timeline for HTMX 161 try { 162 const statuses = await fetchStatusesWithAuthors(); 163 const userTimezone = getUserTimezone(req); 164 const html = render(StatusTimeline({ statuses, userTimezone })); 165 166 return new Response(html, { 167 headers: { "Content-Type": "text/html" }, 168 }); 169 } catch (error) { 170 console.error("Error fetching updated statuses:", error); 171 return new Response("Error loading statuses", { status: 500 }); 172 } 173 } 174 175 return Response.redirect(new URL("/", req.url), 302); 176} 177 178Deno.serve({ port: PORT, hostname: "0.0.0.0" }, handler);