/** * Builds the Oak application and, when run directly, starts the recurring * jobs and listens on {@link LISTEN_HOST}:{@link PORT}, having first signed * the users out of other instances if they may no longer sign in there * (`FEDERATION.outgoing`). A request goes * through the error middleware, the compression of what is answered, the OG * tags and CORS, then the routers, one * per resource of the API (`API.md` lists them), and last the built frontend, * which answers every path no router did. * * @module */ import { Application } from "@oak/oak"; import { oakCors } from "@tajpouria/cors"; import attachmentsRouter from "./routes/attachments.ts"; import avatarsRouter from "./routes/avatars.ts"; import categoriesRouter from "./routes/categories.ts"; import chatRouter from "./routes/chat.ts"; import communityRouter from "./routes/community.ts"; import commentsRouter from "./routes/comments.ts"; import dumpsRouter from "./routes/dumps.ts"; import filesRouter from "./routes/files.ts"; import federationRouter from "./routes/federation.ts"; import followsRouter from "./routes/follows.ts"; import invitesRouter from "./routes/invites.ts"; import meRouter from "./routes/me.ts"; import notificationsRouter from "./routes/notifications.ts"; import oauthRouter from "./routes/oauth.ts"; import passwordResetsRouter from "./routes/password-resets.ts"; import playlistsRouter from "./routes/playlists.ts"; import remoteRouter from "./routes/remote.ts"; import searchRouter from "./routes/search.ts"; import sessionsRouter from "./routes/sessions.ts"; import thumbnailsRouter from "./routes/thumbnails.ts"; import waveformsRouter from "./routes/waveforms.ts"; import unsubscribeRouter from "./routes/unsubscribe.ts"; import usersRouter from "./routes/users.ts"; import wellKnownRouter from "./routes/well-known.ts"; import wsRouter from "./routes/ws.ts"; import { ALLOWED_ORIGINS, FEDERATION, LISTEN_HOST, PORT, PUBLIC_URL, } from "./config.ts"; import { compressMiddleware } from "./middleware/compress.ts"; import { errorMiddleware } from "./middleware/error.ts"; import { ogMiddleware } from "./middleware/og.ts"; import { routeStaticFilesFrom } from "./lib/static.ts"; import { startJobs } from "./lib/scheduler.ts"; import { rateLimitSweepJob } from "./lib/rate-limit.ts"; import { generatedMediaSweepJob } from "./lib/ffmpeg.ts"; import { digestJob } from "./services/digest-service.ts"; import { communityJob } from "./services/community-service.ts"; import { pingJob, typingExpiryJob } from "./services/ws-service.ts"; import { outboxJob, withdrawAllGrants, } from "./services/identity-provider-service.ts"; /** * The whole server, exported so the tests' helpers can send it requests * without listening. */ const app: Application = new Application(); const cors = oakCors({ origin: ALLOWED_ORIGINS }); app.use(errorMiddleware); app.use(compressMiddleware); app.use(ogMiddleware); // oakCors calls next() without awaiting it when there is no origin, which // corrupts the response in Oak's async chain. So it only sees requests from an // allowed origin, and the others get no CORS headers. app.use(async (ctx, next) => { const origin = ctx.request.headers.get("origin"); if (origin && ALLOWED_ORIGINS.includes(origin)) { return await cors(ctx, next); } await next(); }); for ( const router of [ meRouter, followsRouter, notificationsRouter, avatarsRouter, usersRouter, sessionsRouter, passwordResetsRouter, unsubscribeRouter, dumpsRouter, filesRouter, thumbnailsRouter, waveformsRouter, commentsRouter, playlistsRouter, categoriesRouter, searchRouter, chatRouter, communityRouter, invitesRouter, attachmentsRouter, remoteRouter, federationRouter, oauthRouter, wellKnownRouter, wsRouter, ] ) { app.use(router.routes(), router.allowedMethods()); } app.use(routeStaticFilesFrom([ `${Deno.cwd()}/dist`, `${Deno.cwd()}/public`, ])); app.addEventListener( "listen", () => console.log(`Server listening on ${PUBLIC_URL}`), ); app.addEventListener( "error", (e) => console.log(`Uncaught error: ${e.message}`), ); if (import.meta.main) { if (!FEDERATION.outgoing) { const withdrawn = await withdrawAllGrants(); if (withdrawn > 0) { console.log(`Signed users out of other instances (${withdrawn} grants)`); } } startJobs([ digestJob, communityJob, pingJob, typingExpiryJob, rateLimitSweepJob, generatedMediaSweepJob, outboxJob, ]); await app.listen({ hostname: LISTEN_HOST, port: PORT }); } export { app };