diff --git a/docs/src/routes/subscribe.ts b/docs/src/routes/subscribe.ts index 45170be..7e26d69 100644 --- a/docs/src/routes/subscribe.ts +++ b/docs/src/routes/subscribe.ts @@ -154,8 +154,48 @@ subscribe.post("/", async (c) => { subscribe.get("/", async (c) => { const publicationUri = c.req.query("publicationUri"); + const action = c.req.query("action"); + const wantsJson = c.req.header("accept")?.includes("application/json"); + + // JSON path: subscription status check for the web component. + if (wantsJson) { + if (action && action !== "unsubscribe") { + return c.json({ error: `Unsupported action: ${action}` }, 400); + } + if (!publicationUri || !publicationUri.startsWith("at://")) { + return c.json({ error: "Missing or invalid publicationUri" }, 400); + } + const did = getSessionDid(c); + if (!did) { + return c.json({ authenticated: false }, 401); + } + try { + const client = createOAuthClient( + c.env.SEQUOIA_SESSIONS, + c.env.CLIENT_URL, + ); + const session = await client.restore(did); + const agent = new Agent(session); + const recordUri = await findExistingSubscription( + agent, + did, + publicationUri, + ); + return recordUri + ? c.json({ subscribed: true, recordUri }) + : c.json({ subscribed: false }); + } catch { + return c.json({ authenticated: false }, 401); + } + } + + // HTML path: full-page subscribe/unsubscribe flow. const styleHref = await getVocsStyleHref(c.env.ASSETS, c.req.url); + if (action && action !== "unsubscribe") { + return c.html(renderError(`Unsupported action: ${action}`, styleHref), 400); + } + if (!publicationUri || !publicationUri.startsWith("at://")) { return c.html( renderError("Missing or invalid publication URI.", styleHref), @@ -172,7 +212,9 @@ subscribe.get("/", async (c) => { const did = getSessionDid(c); if (!did) { - return c.html(renderHandleForm(publicationUri, styleHref, returnTo)); + return c.html( + renderHandleForm(publicationUri, styleHref, returnTo, undefined, action), + ); } try { @@ -180,6 +222,34 @@ subscribe.get("/", async (c) => { const session = await client.restore(did); const agent = new Agent(session); + if (action === "unsubscribe") { + const existingUri = await findExistingSubscription( + agent, + did, + publicationUri, + ); + if (existingUri) { + const rkey = existingUri.split("/").pop()!; + await agent.com.atproto.repo.deleteRecord({ + repo: did, + collection: COLLECTION, + rkey, + }); + } + return c.html( + renderSuccess( + publicationUri, + null, + "Unsubscribed ✓", + existingUri + ? "You've successfully unsubscribed!" + : "You weren't subscribed to this publication.", + styleHref, + returnTo, + ), + ); + } + const existingUri = await findExistingSubscription( agent, did, @@ -187,7 +257,14 @@ subscribe.get("/", async (c) => { ); if (existingUri) { return c.html( - renderSuccess(publicationUri, existingUri, true, styleHref, returnTo), + renderSuccess( + publicationUri, + existingUri, + "Subscribed ✓", + "You're already subscribed to this publication.", + styleHref, + returnTo, + ), ); } @@ -204,7 +281,8 @@ subscribe.get("/", async (c) => { renderSuccess( publicationUri, result.data.uri, - false, + "Subscribed ✓", + "You've successfully subscribed!", styleHref, returnTo, ), @@ -218,6 +296,7 @@ subscribe.get("/", async (c) => { styleHref, returnTo, "Session expired. Please sign in again.", + action, ), ); } @@ -235,6 +314,7 @@ subscribe.post("/login", async (c) => { const handle = (body["handle"] as string | undefined)?.trim(); const publicationUri = body["publicationUri"] as string | undefined; const formReturnTo = (body["returnTo"] as string | undefined) || undefined; + const formAction = (body["action"] as string | undefined) || undefined; if (!handle || !publicationUri) { const styleHref = await getVocsStyleHref(c.env.ASSETS, c.req.url); @@ -246,6 +326,7 @@ subscribe.post("/login", async (c) => { const returnTo = `${c.env.CLIENT_URL}/subscribe?publicationUri=${encodeURIComponent(publicationUri)}` + + (formAction ? `&action=${encodeURIComponent(formAction)}` : "") + (formReturnTo ? `&returnTo=${encodeURIComponent(formReturnTo)}` : ""); setReturnToCookie(c, returnTo, c.env.CLIENT_URL); @@ -263,6 +344,7 @@ function renderHandleForm( styleHref: string, returnTo?: string, error?: string, + action?: string, ): string { const errorHtml = error ? `
${escapeHtml(error)}
` @@ -270,6 +352,9 @@ function renderHandleForm( const returnToInput = returnTo ? `` : ""; + const actionInput = action + ? `` + : ""; return page( ` @@ -279,6 +364,7 @@ function renderHandleForm(