jevstrudel.git / worker / src / index.ts

The jevstrudel Worker. The static site is served by Cloudflare's asset layer before this runs (wrangler deploy --assets), so the Worker only sees paths that are not files:

POST /jev/v1/systemone the Jev relay (relay.ts), everywhere POST /jev/votes a "do you agree with Jev?" vote } votes.ts, GET /jev/votes/summary the vote counts } everywhere … /jev/auth/* passkey accounts (auth.ts): register, sign in and out, who is signed in; everywhere GET, POST /jev/me/radio a signed-in listener's radio history (radio.ts) POST /jev/reactions a listener's 🔥 or 😴 } listening.ts, GET /jev/reactions a song's tally, all listeners } everywhere POST /jev/performances a performance, for a replay link } GET /jev/performances/<id> one, to replay it } GET /jev/party/<room> a listening party's WebSocket (party.ts), everywhere GET /jev/parties[/<id>] live parties, and a listed one's room (party.ts) GET /jev/lobby the lobby's WebSocket: who is listening to what (lobby.ts), everywhere GET /jev/data[/] everything the site stores, for anyone to read (data.ts): people too, never a secret; everywhere … /jev/listeners/ listeners' songs, covers, comments and pitches (content.ts): public to read, signed in to write, screened by Jev first; everywhere POST /jev/mcp the hosted MCP, for everyone's own AI, with an OAuth token (hosted-mcp.ts); its OAuth under /jev/oauth/* and /.well-known/oauth-* (oauth.ts); /jev/me/tabs, a signed-in tab joining its user's hub, and /jev/me/apps, connected apps; everywhere GET /strudel/status open MCP tabs; the page's probe } dev only: GET /strudel/ws a tab's WebSocket to the hub } they need POST /mcp MCP over HTTP (mcp.ts) } the HUB binding

Its cron trigger (wrangler.json) runs the work Jev owes listeners' content, screening and scoring, every minute (content-jobs.ts).

36import { auth, AUTH_PREFIX } from './auth';
37import { content, LISTENERS_PREFIX } from './content';
38import { data, DATA_PREFIX } from './data';
39import { sweep } from './content-jobs';
40import { d1Content } from './content-store';
41import type { Env } from './env';
42import { mcp } from './mcp';
43import { oauth } from './oauth';
44import { radio, RADIO_PATH } from './radio';
45import { listening, PERFORMANCES_PATH, REACTIONS_PATH } from './listening';
46import { d1Listening } from './listening-store';
47import { lobby, LOBBY_PATH } from './lobby';
48import { parties, PARTIES_PATH, party, PARTY_PREFIX } from './party';
49import { relay, RELAY_PATH } from './relay';
50import { refuseCrossOrigin } from './session';
51import { SUMMARY_PATH, votes, VOTES_PATH } from './votes';
52import { d1Votes } from './votes-store';
54export { JevBudget } from './budget';
55export { Hub } from './hub';
56export { LobbyRoom } from './lobby';
57export { Party, PartyDirectory } from './party';
58export { TabHub } from './tab-hub';
59
60export default {
61  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
62    const { pathname } = new URL(request.url);
63    // writes only from this site's own pages: never another site's, nor the
64    // sandbox listeners' songs play in (session.ts)
65    const crossOrigin = refuseCrossOrigin(request);
66    if (crossOrigin) return crossOrigin;
67    if (pathname === RELAY_PATH) return relay(request, env, ctx);
68    const hosted = await oauth(request, env, ctx);
69    if (hosted) return hosted;
70    if (pathname === VOTES_PATH || pathname === SUMMARY_PATH) {
71      return votes(request, env, d1Votes(env.DB));
72    }
73    if (pathname.startsWith(AUTH_PREFIX)) return auth(request, env);
74    if (pathname === RADIO_PATH) return radio(request, env);
75    if (pathname === REACTIONS_PATH || pathname === PERFORMANCES_PATH || pathname.startsWith(`${PERFORMANCES_PATH}/`)) {
76      return listening(request, env, d1Listening(env.DB));
77    }
78    if (pathname.startsWith(PARTY_PREFIX)) return party(request, env);
79    if (pathname === PARTIES_PATH || pathname.startsWith(`${PARTIES_PATH}/`)) return parties(request, env);
80    if (pathname === LOBBY_PATH) return lobby(request, env);
81    if (pathname.startsWith(LISTENERS_PREFIX)) return content(request, env, ctx);
82    if (pathname === DATA_PREFIX || pathname.startsWith(`${DATA_PREFIX}/`)) return data(request, env);
83    if (pathname === '/mcp') return mcp(request, env);
84    if (env.HUB && pathname === '/strudel/ws') return env.HUB.get(env.HUB.idFromName('hub')).fetch(request);
85    if (env.HUB && pathname === '/strudel/status') {
86      const sessions = await env.HUB.get(env.HUB.idFromName('hub')).sessions();
87      return Response.json({ active_sessions: sessions });
88    }
89    return new Response('not found', { status: 404 });
90  },
91
92  async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext): Promise<void> {
93    ctx.waitUntil(sweep(env, d1Content(env.DB)).then(() => undefined));
94  },
95} satisfies ExportedHandler<Env>;