diff options
| author | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 01:42:57 -0800 |
| commit | 5c5b1993edd890a80870ee05607ac5f088191d4e (patch) | |
| tree | a721b76bcd49ba10826c53efc87302c7a689512f /apps/web/app/api/account | |
| download | asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.tar.xz asa.news-5c5b1993edd890a80870ee05607ac5f088191d4e.zip | |
feat: asa.news RSS reader with developer tier, REST API, and webhooks
Full-stack RSS reader SaaS: Supabase + Next.js + Go worker.
Includes three subscription tiers (free/pro/developer), API key auth,
read-only REST API, webhook push notifications, Stripe billing with
proration, and PWA support.
Diffstat (limited to 'apps/web/app/api/account')
| -rw-r--r-- | apps/web/app/api/account/data/route.ts | 96 | ||||
| -rw-r--r-- | apps/web/app/api/account/route.ts | 27 |
2 files changed, 123 insertions, 0 deletions
diff --git a/apps/web/app/api/account/data/route.ts b/apps/web/app/api/account/data/route.ts new file mode 100644 index 0000000..dbee725 --- /dev/null +++ b/apps/web/app/api/account/data/route.ts @@ -0,0 +1,96 @@ +import { NextResponse } from "next/server" +import { createSupabaseServerClient } from "@/lib/supabase/server" + +export async function GET() { + const supabaseClient = await createSupabaseServerClient() + const { + data: { user }, + } = await supabaseClient.auth.getUser() + + if (!user) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + } + + const [ + profileResult, + subscriptionsResult, + foldersResult, + mutedKeywordsResult, + customFeedsResult, + entryStatesResult, + highlightsResult, + sharedEntriesResult, + savedEntriesResult, + ] = await Promise.all([ + supabaseClient + .from("user_profiles") + .select("id, display_name, tier, created_at") + .eq("id", user.id) + .single(), + supabaseClient + .from("subscriptions") + .select("id, feed_id, folder_id, custom_title, created_at, feeds(title, url)") + .eq("user_id", user.id), + supabaseClient + .from("folders") + .select("id, name, position, created_at") + .eq("user_id", user.id), + supabaseClient + .from("muted_keywords") + .select("id, keyword, created_at") + .eq("user_id", user.id), + supabaseClient + .from("custom_feeds") + .select("id, name, query, position, created_at") + .eq("user_id", user.id), + supabaseClient + .from("user_entry_states") + .select("entry_id, read, saved, updated_at") + .eq("user_id", user.id), + supabaseClient + .from("user_highlights") + .select( + "id, entry_id, highlighted_text, note, color, text_offset, text_length, created_at, entries(title, url)" + ) + .eq("user_id", user.id), + supabaseClient + .from("shared_entries") + .select("id, entry_id, share_token, created_at, entries(title, url)") + .eq("user_id", user.id), + supabaseClient + .from("user_entry_states") + .select( + "entries(id, title, url, author, summary, published_at, feeds(title, url))" + ) + .eq("user_id", user.id) + .eq("saved", true), + ]) + + const exportData = { + exportedAt: new Date().toISOString(), + account: { + emailAddress: user.email, + ...profileResult.data, + }, + subscriptions: subscriptionsResult.data ?? [], + folders: foldersResult.data ?? [], + mutedKeywords: mutedKeywordsResult.data ?? [], + customFeeds: customFeedsResult.data ?? [], + entryStates: entryStatesResult.data ?? [], + highlights: highlightsResult.data ?? [], + sharedEntries: sharedEntriesResult.data ?? [], + savedEntries: + (savedEntriesResult.data ?? []).map( + (row) => (row as Record<string, unknown>).entries + ) ?? [], + } + + const jsonString = JSON.stringify(exportData, null, 2) + + return new Response(jsonString, { + headers: { + "Content-Type": "application/json", + "Content-Disposition": `attachment; filename="asa-news-gdpr-export-${new Date().toISOString().slice(0, 10)}.json"`, + }, + }) +} diff --git a/apps/web/app/api/account/route.ts b/apps/web/app/api/account/route.ts new file mode 100644 index 0000000..6b1bc2d --- /dev/null +++ b/apps/web/app/api/account/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from "next/server" +import { createSupabaseServerClient } from "@/lib/supabase/server" +import { createSupabaseAdminClient } from "@/lib/supabase/admin" + +export async function DELETE() { + const supabaseClient = await createSupabaseServerClient() + const { + data: { user }, + } = await supabaseClient.auth.getUser() + + if (!user) { + return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + } + + const adminClient = createSupabaseAdminClient() + + const { error } = await adminClient.auth.admin.deleteUser(user.id) + + if (error) { + return NextResponse.json( + { error: "Failed to delete account" }, + { status: 500 } + ) + } + + return new Response(null, { status: 204 }) +} |