diff options
| author | Fuwn <[email protected]> | 2026-02-07 02:00:59 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-07 02:00:59 -0800 |
| commit | f93bad7da47093a12116ff0f390abb548289b600 (patch) | |
| tree | e2a9debcca3473af8f293c3215704549e5bde17f /apps | |
| parent | style: format Go worker with iku (diff) | |
| download | asa.news-f93bad7da47093a12116ff0f390abb548289b600.tar.xz asa.news-f93bad7da47093a12116ff0f390abb548289b600.zip | |
style: lowercase all user-facing strings and add custom eslint rule
Comprehensive sweep of all user-facing text to enforce lowercase
convention, including acronyms (api, rest, http, opml, json, totp,
mfa, qr, hmac). Added asa-lowercase/lowercase-strings eslint rule
that reports uppercase in notify() calls, error messages, jsx text,
and checked attributes (placeholder, alt, title).
Diffstat (limited to 'apps')
41 files changed, 402 insertions, 132 deletions
diff --git a/apps/web/app/(marketing)/_components/feature-grid.tsx b/apps/web/app/(marketing)/_components/feature-grid.tsx index 64fd1a4..607e82f 100644 --- a/apps/web/app/(marketing)/_components/feature-grid.tsx +++ b/apps/web/app/(marketing)/_components/feature-grid.tsx @@ -22,7 +22,7 @@ const FEATURES = [ { title: "import & export", description: - "import your feeds from any reader via OPML. pro users can export their full data.", + "import your feeds from any reader via opml. pro users can export their full data.", }, { title: "real-time updates", diff --git a/apps/web/app/api/account/data/route.ts b/apps/web/app/api/account/data/route.ts index dbee725..bec6ab9 100644 --- a/apps/web/app/api/account/data/route.ts +++ b/apps/web/app/api/account/data/route.ts @@ -8,7 +8,7 @@ export async function GET() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const [ diff --git a/apps/web/app/api/account/route.ts b/apps/web/app/api/account/route.ts index 6b1bc2d..35408d7 100644 --- a/apps/web/app/api/account/route.ts +++ b/apps/web/app/api/account/route.ts @@ -9,7 +9,7 @@ export async function DELETE() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const adminClient = createSupabaseAdminClient() @@ -18,7 +18,7 @@ export async function DELETE() { if (error) { return NextResponse.json( - { error: "Failed to delete account" }, + { error: "failed to delete account" }, { status: 500 } ) } diff --git a/apps/web/app/api/billing/create-checkout-session/route.ts b/apps/web/app/api/billing/create-checkout-session/route.ts index cfbb388..d165cbc 100644 --- a/apps/web/app/api/billing/create-checkout-session/route.ts +++ b/apps/web/app/api/billing/create-checkout-session/route.ts @@ -12,12 +12,12 @@ export async function POST(request: Request) { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const rateLimitResult = rateLimit(`checkout:${user.id}`, 10, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const body = await request.json().catch(() => ({})) @@ -38,7 +38,7 @@ export async function POST(request: Request) { if (!stripePriceIdentifier) { return NextResponse.json( - { error: "Invalid plan configuration" }, + { error: "invalid plan configuration" }, { status: 500 } ) } @@ -51,7 +51,7 @@ export async function POST(request: Request) { if (profileError || !profile) { return NextResponse.json( - { error: "Failed to load profile" }, + { error: "failed to load profile" }, { status: 500 } ) } @@ -62,7 +62,7 @@ export async function POST(request: Request) { if (currentRank >= targetRank) { return NextResponse.json( - { error: `Already on ${profile.tier} plan` }, + { error: `already on ${profile.tier} plan` }, { status: 400 } ) } @@ -76,7 +76,7 @@ export async function POST(request: Request) { if (!existingItemIdentifier) { return NextResponse.json( - { error: "Could not find existing subscription item" }, + { error: "could not find existing subscription item" }, { status: 500 } ) } @@ -123,7 +123,7 @@ export async function POST(request: Request) { if (updateError) { console.error("Admin client update error:", updateError) return NextResponse.json( - { error: "Failed to save customer: " + updateError.message }, + { error: "failed to save customer: " + updateError.message }, { status: 500 } ) } diff --git a/apps/web/app/api/billing/create-portal-session/route.ts b/apps/web/app/api/billing/create-portal-session/route.ts index 3832c0d..29698e2 100644 --- a/apps/web/app/api/billing/create-portal-session/route.ts +++ b/apps/web/app/api/billing/create-portal-session/route.ts @@ -11,12 +11,12 @@ export async function POST() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const rateLimitResult = rateLimit(`portal:${user.id}`, 10, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const { data: profile, error: profileError } = await supabaseClient @@ -27,14 +27,14 @@ export async function POST() { if (profileError || !profile) { return NextResponse.json( - { error: "Failed to load profile" }, + { error: "failed to load profile" }, { status: 500 } ) } if (!profile.stripe_customer_identifier) { return NextResponse.json( - { error: "No billing account found" }, + { error: "no billing account found" }, { status: 400 } ) } diff --git a/apps/web/app/api/billing/webhook/route.ts b/apps/web/app/api/billing/webhook/route.ts index 8aed7d0..297ef8e 100644 --- a/apps/web/app/api/billing/webhook/route.ts +++ b/apps/web/app/api/billing/webhook/route.ts @@ -44,7 +44,7 @@ async function updateBillingState( .eq("stripe_customer_identifier", stripeCustomerIdentifier) if (error) { - console.error("Failed to update billing state:", error) + console.error("failed to update billing state:", error) } } @@ -131,14 +131,14 @@ export async function POST(request: Request) { const clientIp = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown" const rateLimitResult = rateLimit(`webhook:${clientIp}`, 60, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const body = await request.text() const signature = request.headers.get("stripe-signature") if (!signature) { - return NextResponse.json({ error: "Missing signature" }, { status: 400 }) + return NextResponse.json({ error: "missing signature" }, { status: 400 }) } let event: Stripe.Event @@ -150,7 +150,7 @@ export async function POST(request: Request) { process.env.STRIPE_WEBHOOK_SECRET! ) } catch { - return NextResponse.json({ error: "Invalid signature" }, { status: 400 }) + return NextResponse.json({ error: "invalid signature" }, { status: 400 }) } switch (event.type) { diff --git a/apps/web/app/api/export/route.ts b/apps/web/app/api/export/route.ts index 4842f83..4d15c5a 100644 --- a/apps/web/app/api/export/route.ts +++ b/apps/web/app/api/export/route.ts @@ -8,7 +8,7 @@ export async function GET() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const { data: profile } = await supabaseClient diff --git a/apps/web/app/api/share/[token]/route.ts b/apps/web/app/api/share/[token]/route.ts index 45224aa..d1d57b5 100644 --- a/apps/web/app/api/share/[token]/route.ts +++ b/apps/web/app/api/share/[token]/route.ts @@ -13,7 +13,7 @@ export async function DELETE( } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const { token } = await params @@ -26,7 +26,7 @@ export async function DELETE( if (error) { return NextResponse.json( - { error: "Failed to delete share" }, + { error: "failed to delete share" }, { status: 500 } ) } @@ -44,7 +44,7 @@ export async function PATCH( } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const { token } = await params @@ -76,7 +76,7 @@ export async function PATCH( if (error) { return NextResponse.json( - { error: "Failed to update share" }, + { error: "failed to update share" }, { status: 500 } ) } diff --git a/apps/web/app/api/share/route.ts b/apps/web/app/api/share/route.ts index 2558560..f330bd0 100644 --- a/apps/web/app/api/share/route.ts +++ b/apps/web/app/api/share/route.ts @@ -22,7 +22,7 @@ export async function POST(request: Request) { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const { data: userProfile } = await supabaseClient @@ -73,7 +73,7 @@ export async function POST(request: Request) { if (!entryAccess) { return NextResponse.json( - { error: "Entry not found or not accessible" }, + { error: "entry not found or not accessible" }, { status: 404 } ) } @@ -87,7 +87,7 @@ export async function POST(request: Request) { if (!subscriptionAccess) { return NextResponse.json( - { error: "You do not have access to this entry" }, + { error: "you do not have access to this entry" }, { status: 403 } ) } @@ -121,7 +121,7 @@ export async function POST(request: Request) { if (error) { return NextResponse.json( - { error: "Failed to create share" }, + { error: "failed to create share" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/entries/[entryIdentifier]/route.ts b/apps/web/app/api/v1/entries/[entryIdentifier]/route.ts index 157366b..d420f92 100644 --- a/apps/web/app/api/v1/entries/[entryIdentifier]/route.ts +++ b/apps/web/app/api/v1/entries/[entryIdentifier]/route.ts @@ -28,7 +28,7 @@ export async function GET( .single() if (error || !entry) { - return NextResponse.json({ error: "Entry not found" }, { status: 404 }) + return NextResponse.json({ error: "entry not found" }, { status: 404 }) } const { data: subscription } = await adminClient @@ -39,7 +39,7 @@ export async function GET( .single() if (!subscription) { - return NextResponse.json({ error: "Entry not found" }, { status: 404 }) + return NextResponse.json({ error: "entry not found" }, { status: 404 }) } const { data: stateRow } = await adminClient diff --git a/apps/web/app/api/v1/entries/route.ts b/apps/web/app/api/v1/entries/route.ts index 653c79b..e782e3b 100644 --- a/apps/web/app/api/v1/entries/route.ts +++ b/apps/web/app/api/v1/entries/route.ts @@ -57,7 +57,7 @@ export async function GET(request: Request) { if (error) { return NextResponse.json( - { error: "Failed to load entries" }, + { error: "failed to load entries" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/feeds/route.ts b/apps/web/app/api/v1/feeds/route.ts index adf5422..5b59856 100644 --- a/apps/web/app/api/v1/feeds/route.ts +++ b/apps/web/app/api/v1/feeds/route.ts @@ -22,7 +22,7 @@ export async function GET(request: Request) { if (error) { return NextResponse.json( - { error: "Failed to load feeds" }, + { error: "failed to load feeds" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/folders/route.ts b/apps/web/app/api/v1/folders/route.ts index 5fb006d..3b808a6 100644 --- a/apps/web/app/api/v1/folders/route.ts +++ b/apps/web/app/api/v1/folders/route.ts @@ -21,7 +21,7 @@ export async function GET(request: Request) { if (error) { return NextResponse.json( - { error: "Failed to load folders" }, + { error: "failed to load folders" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/keys/[keyIdentifier]/route.ts b/apps/web/app/api/v1/keys/[keyIdentifier]/route.ts index 8026f27..9835227 100644 --- a/apps/web/app/api/v1/keys/[keyIdentifier]/route.ts +++ b/apps/web/app/api/v1/keys/[keyIdentifier]/route.ts @@ -12,7 +12,7 @@ export async function DELETE( } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const { keyIdentifier } = await params @@ -27,7 +27,7 @@ export async function DELETE( if (error) { return NextResponse.json( - { error: "Failed to revoke API key" }, + { error: "failed to revoke api key" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/keys/route.ts b/apps/web/app/api/v1/keys/route.ts index 7ac7144..1461532 100644 --- a/apps/web/app/api/v1/keys/route.ts +++ b/apps/web/app/api/v1/keys/route.ts @@ -14,7 +14,7 @@ export async function GET() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const adminClient = createSupabaseAdminClient() @@ -26,7 +26,7 @@ export async function GET() { if (error) { return NextResponse.json( - { error: "Failed to load API keys" }, + { error: "failed to load api keys" }, { status: 500 } ) } @@ -50,12 +50,12 @@ export async function POST(request: Request) { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const rateLimitResult = rateLimit(`api-keys:${user.id}`, 10, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const adminClient = createSupabaseAdminClient() @@ -71,7 +71,7 @@ export async function POST(request: Request) { !TIER_LIMITS[userProfile.tier as SubscriptionTier]?.allowsApiAccess ) { return NextResponse.json( - { error: "API access requires the developer plan" }, + { error: "api access requires the developer plan" }, { status: 403 } ) } @@ -84,7 +84,7 @@ export async function POST(request: Request) { if ((activeKeyCount ?? 0) >= MAXIMUM_ACTIVE_KEYS) { return NextResponse.json( - { error: `Maximum of ${MAXIMUM_ACTIVE_KEYS} active keys allowed` }, + { error: `maximum of ${MAXIMUM_ACTIVE_KEYS} active keys allowed` }, { status: 400 } ) } @@ -103,7 +103,7 @@ export async function POST(request: Request) { if (insertError) { return NextResponse.json( - { error: "Failed to create API key" }, + { error: "failed to create api key" }, { status: 500 } ) } diff --git a/apps/web/app/api/v1/profile/route.ts b/apps/web/app/api/v1/profile/route.ts index f7ec308..a7773dd 100644 --- a/apps/web/app/api/v1/profile/route.ts +++ b/apps/web/app/api/v1/profile/route.ts @@ -24,7 +24,7 @@ export async function GET(request: Request) { if (error || !profile) { return NextResponse.json( - { error: "Failed to load profile" }, + { error: "failed to load profile" }, { status: 500 } ) } diff --git a/apps/web/app/api/webhook-config/route.ts b/apps/web/app/api/webhook-config/route.ts index 1ce9a30..049f4f3 100644 --- a/apps/web/app/api/webhook-config/route.ts +++ b/apps/web/app/api/webhook-config/route.ts @@ -11,7 +11,7 @@ export async function GET() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const adminClient = createSupabaseAdminClient() @@ -25,7 +25,7 @@ export async function GET() { if (error || !profile) { return NextResponse.json( - { error: "Failed to load webhook config" }, + { error: "failed to load webhook config" }, { status: 500 } ) } @@ -45,12 +45,12 @@ export async function PUT(request: Request) { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const rateLimitResult = rateLimit(`webhook-config:${user.id}`, 10, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const adminClient = createSupabaseAdminClient() @@ -66,7 +66,7 @@ export async function PUT(request: Request) { !TIER_LIMITS[profile.tier as SubscriptionTier]?.allowsWebhooks ) { return NextResponse.json( - { error: "Webhooks require the developer plan" }, + { error: "webhooks require the developer plan" }, { status: 403 } ) } @@ -79,7 +79,7 @@ export async function PUT(request: Request) { const trimmedUrl = body.webhookUrl.trim() if (trimmedUrl && !trimmedUrl.startsWith("https://")) { return NextResponse.json( - { error: "Webhook URL must use HTTPS" }, + { error: "webhook url must use https" }, { status: 400 } ) } @@ -98,7 +98,7 @@ export async function PUT(request: Request) { } if (Object.keys(updates).length === 0) { - return NextResponse.json({ error: "No updates provided" }, { status: 400 }) + return NextResponse.json({ error: "no updates provided" }, { status: 400 }) } const { error } = await adminClient @@ -108,7 +108,7 @@ export async function PUT(request: Request) { if (error) { return NextResponse.json( - { error: "Failed to update webhook config" }, + { error: "failed to update webhook config" }, { status: 500 } ) } diff --git a/apps/web/app/api/webhook-config/test/route.ts b/apps/web/app/api/webhook-config/test/route.ts index 684ec0c..6171da4 100644 --- a/apps/web/app/api/webhook-config/test/route.ts +++ b/apps/web/app/api/webhook-config/test/route.ts @@ -12,12 +12,12 @@ export async function POST() { } = await supabaseClient.auth.getUser() if (!user) { - return NextResponse.json({ error: "Not authenticated" }, { status: 401 }) + return NextResponse.json({ error: "not authenticated" }, { status: 401 }) } const rateLimitResult = rateLimit(`webhook-test:${user.id}`, 5, 60_000) if (!rateLimitResult.success) { - return NextResponse.json({ error: "Too many requests" }, { status: 429 }) + return NextResponse.json({ error: "too many requests" }, { status: 429 }) } const adminClient = createSupabaseAdminClient() @@ -34,14 +34,14 @@ export async function POST() { !TIER_LIMITS[profile.tier as SubscriptionTier]?.allowsWebhooks ) { return NextResponse.json( - { error: "Webhooks require the developer plan" }, + { error: "webhooks require the developer plan" }, { status: 403 } ) } if (!profile.webhook_url) { return NextResponse.json( - { error: "No webhook URL configured" }, + { error: "no webhook url configured" }, { status: 400 } ) } @@ -53,7 +53,7 @@ export async function POST() { { entryIdentifier: "test-entry-000", feedIdentifier: "test-feed-000", - title: "Test webhook delivery", + title: "test webhook delivery", url: "https://asa.news", author: "asa.news", summary: "This is a test webhook payload to verify your endpoint.", diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index a3e3b8b..27c95bc 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -14,7 +14,7 @@ const jetBrainsMono = localFont({ export const metadata: Metadata = { title: "asa.news", - description: "A fast, minimal RSS reader for staying informed", + description: "a fast, minimal rss reader for staying informed", appleWebApp: { capable: true, statusBarStyle: "black-translucent", diff --git a/apps/web/app/manifest.ts b/apps/web/app/manifest.ts index 0bef8b1..cb940d5 100644 --- a/apps/web/app/manifest.ts +++ b/apps/web/app/manifest.ts @@ -4,7 +4,7 @@ export default function manifest(): MetadataRoute.Manifest { return { name: "asa.news", short_name: "asa.news", - description: "A dense, keyboard-first RSS reader", + description: "a dense, keyboard-first rss reader", start_url: "/reader", display: "standalone", background_color: "#0a0a0a", diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx index 2e8e19c..b823fe7 100644 --- a/apps/web/app/reader/_components/entry-detail-panel.tsx +++ b/apps/web/app/reader/_components/entry-detail-panel.tsx @@ -112,7 +112,7 @@ export function EntryDetailPanel({ headers: { "Content-Type": "application/json" }, body: JSON.stringify({ entryIdentifier, note: note ?? null }), }) - if (!response.ok) throw new Error("Failed to create share") + if (!response.ok) throw new Error("failed to create share") return response.json() as Promise<{ shareToken: string shareUrl: string @@ -133,7 +133,7 @@ export function EntryDetailPanel({ const response = await fetch(`/api/share/${shareToken}`, { method: "DELETE", }) - if (!response.ok) throw new Error("Failed to delete share") + if (!response.ok) throw new Error("failed to delete share") }, onSuccess: () => { notify("share link removed") diff --git a/apps/web/app/reader/settings/_components/account-settings.tsx b/apps/web/app/reader/settings/_components/account-settings.tsx index b9ed8c3..ccb09dd 100644 --- a/apps/web/app/reader/settings/_components/account-settings.tsx +++ b/apps/web/app/reader/settings/_components/account-settings.tsx @@ -29,7 +29,7 @@ export function AccountSettings() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient .from("user_profiles") @@ -59,7 +59,7 @@ export function AccountSettings() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user?.email) throw new Error("Not authenticated") + if (!user?.email) throw new Error("not authenticated") const { error: signInError } = await supabaseClient.auth.signInWithPassword({ email: user.email, @@ -96,7 +96,7 @@ export function AccountSettings() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user?.email) throw new Error("Not authenticated") + if (!user?.email) throw new Error("not authenticated") const { error: signInError } = await supabaseClient.auth.signInWithPassword({ email: user.email, @@ -139,7 +139,7 @@ export function AccountSettings() { setIsRequestingData(true) try { const response = await fetch("/api/account/data") - if (!response.ok) throw new Error("Export failed") + if (!response.ok) throw new Error("export failed") const blob = await response.blob() const url = URL.createObjectURL(blob) const anchor = document.createElement("a") diff --git a/apps/web/app/reader/settings/_components/api-settings.tsx b/apps/web/app/reader/settings/_components/api-settings.tsx index cb69958..0ae6a8d 100644 --- a/apps/web/app/reader/settings/_components/api-settings.tsx +++ b/apps/web/app/reader/settings/_components/api-settings.tsx @@ -26,7 +26,7 @@ function useApiKeys() { queryKey: ["apiKeys"], queryFn: async () => { const response = await fetch("/api/v1/keys") - if (!response.ok) throw new Error("Failed to load API keys") + if (!response.ok) throw new Error("failed to load api keys") const data = await response.json() return data.keys as ApiKey[] }, @@ -44,7 +44,7 @@ function useCreateApiKey() { }) if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to create API key") + throw new Error(data.error || "failed to create api key") } return response.json() as Promise<{ fullKey: string @@ -67,7 +67,7 @@ function useRevokeApiKey() { }) if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to revoke API key") + throw new Error(data.error || "failed to revoke api key") } }, onSuccess: () => { @@ -81,7 +81,7 @@ function useWebhookConfig() { queryKey: ["webhookConfig"], queryFn: async () => { const response = await fetch("/api/webhook-config") - if (!response.ok) throw new Error("Failed to load webhook config") + if (!response.ok) throw new Error("failed to load webhook config") return response.json() as Promise<WebhookConfiguration> }, }) @@ -104,7 +104,7 @@ function useUpdateWebhookConfig() { }) if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to update webhook config") + throw new Error(data.error || "failed to update webhook config") } }, onSuccess: () => { @@ -121,7 +121,7 @@ function useTestWebhook() { }) if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to send test webhook") + throw new Error(data.error || "failed to send test webhook") } return response.json() as Promise<{ delivered: boolean @@ -147,7 +147,7 @@ function ApiKeysSection() { onSuccess: (data) => { setRevealedKey(data.fullKey) setNewKeyLabel("") - notify("API key created") + notify("api key created") }, onError: (error: Error) => { notify(error.message) @@ -158,14 +158,14 @@ function ApiKeysSection() { function handleCopyKey() { if (revealedKey) { navigator.clipboard.writeText(revealedKey) - notify("API key copied to clipboard") + notify("api key copied to clipboard") } } function handleRevokeKey(keyIdentifier: string) { revokeApiKey.mutate(keyIdentifier, { onSuccess: () => { - notify("API key revoked") + notify("api key revoked") setConfirmRevokeIdentifier(null) }, onError: (error: Error) => { @@ -176,9 +176,9 @@ function ApiKeysSection() { return ( <div className="mb-6"> - <h3 className="mb-2 text-text-primary">API keys</h3> + <h3 className="mb-2 text-text-primary">api keys</h3> <p className="mb-3 text-text-dim"> - use API keys to authenticate requests to the REST API + use api keys to authenticate requests to the rest api </p> {revealedKey && ( @@ -379,12 +379,12 @@ function WebhookSection() { <div className="mb-6"> <h3 className="mb-2 text-text-primary">webhooks</h3> <p className="mb-3 text-text-dim"> - receive HTTP POST notifications when new entries arrive in your + receive http post notifications when new entries arrive in your subscribed feeds </p> <div className="mb-4"> - <label className="mb-1 block text-text-secondary">webhook URL</label> + <label className="mb-1 block text-text-secondary">webhook url</label> <input type="url" value={webhookUrl} @@ -409,7 +409,7 @@ function WebhookSection() { setWebhookSecret(event.target.value) setHasUnsavedChanges(true) }} - placeholder="optional HMAC-SHA256 signing secret" + placeholder="optional hmac-sha256 signing secret" className="min-w-0 flex-1 border border-border bg-background-primary px-3 py-1.5 text-text-primary outline-none placeholder:text-text-dim focus:border-text-dim" /> <button @@ -479,21 +479,21 @@ export function ApiSettings() { const { data: userProfile, isLoading } = useUserProfile() if (isLoading) { - return <p className="px-4 py-6 text-text-dim">loading API settings ...</p> + return <p className="px-4 py-6 text-text-dim">loading api settings ...</p> } if (!userProfile) { return ( - <p className="px-4 py-6 text-text-dim">failed to load API settings</p> + <p className="px-4 py-6 text-text-dim">failed to load api settings</p> ) } if (userProfile.tier !== "developer") { return ( <div className="px-4 py-3"> - <h3 className="mb-2 text-text-primary">developer API</h3> + <h3 className="mb-2 text-text-primary">developer api</h3> <p className="mb-3 text-text-dim"> - the developer plan includes a read-only REST API and webhook push + the developer plan includes a read-only rest api and webhook push notifications. upgrade to developer to access these features. </p> </div> @@ -506,22 +506,22 @@ export function ApiSettings() { <WebhookSection /> <div> - <h3 className="mb-2 text-text-primary">API documentation</h3> + <h3 className="mb-2 text-text-primary">api documentation</h3> <p className="mb-3 text-text-dim"> - authenticate requests with an API key in the Authorization header: + authenticate requests with an api key in the authorization header: </p> <code className="block bg-background-tertiary px-3 py-2 text-text-secondary"> Authorization: Bearer asn_your_key_here </code> <div className="mt-3 space-y-1 text-text-dim"> - <p>GET /api/v1/profile — your account info and limits</p> - <p>GET /api/v1/feeds — your subscribed feeds</p> - <p>GET /api/v1/folders — your folders</p> + <p>get /api/v1/profile — your account info and limits</p> + <p>get /api/v1/feeds — your subscribed feeds</p> + <p>get /api/v1/folders — your folders</p> <p> - GET /api/v1/entries — entries with ?cursor, ?limit, ?feedIdentifier, + get /api/v1/entries — entries with ?cursor, ?limit, ?feedIdentifier, ?readStatus, ?savedStatus filters </p> - <p>GET /api/v1/entries/:id — single entry with full content</p> + <p>get /api/v1/entries/:id — single entry with full content</p> </div> </div> </div> diff --git a/apps/web/app/reader/settings/_components/billing-settings.tsx b/apps/web/app/reader/settings/_components/billing-settings.tsx index e49720a..4c62f16 100644 --- a/apps/web/app/reader/settings/_components/billing-settings.tsx +++ b/apps/web/app/reader/settings/_components/billing-settings.tsx @@ -26,7 +26,7 @@ function useCreateCheckoutSession() { if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to create checkout session") + throw new Error(data.error || "failed to create checkout session") } const data = await response.json() @@ -44,7 +44,7 @@ function useCreatePortalSession() { if (!response.ok) { const data = await response.json() - throw new Error(data.error || "Failed to create portal session") + throw new Error(data.error || "failed to create portal session") } const data = await response.json() @@ -58,14 +58,14 @@ const PRO_FEATURES = [ `${Number.isFinite(TIER_LIMITS.pro.historyRetentionDays) ? TIER_LIMITS.pro.historyRetentionDays.toLocaleString() + " days" : "unlimited"} history retention`, `${TIER_LIMITS.pro.refreshIntervalSeconds / 60}-minute refresh interval`, "authenticated feeds", - "OPML export", + "opml export", "manual feed refresh", ] const DEVELOPER_FEATURES = [ `${TIER_LIMITS.developer.maximumFeeds} feeds`, "everything in pro", - "read-only REST API", + "read-only rest api", "webhook push notifications", ] diff --git a/apps/web/app/reader/settings/_components/danger-zone-settings.tsx b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx index 76c48d4..3525426 100644 --- a/apps/web/app/reader/settings/_components/danger-zone-settings.tsx +++ b/apps/web/app/reader/settings/_components/danger-zone-settings.tsx @@ -19,7 +19,7 @@ export function DangerZoneSettings() { const deleteAccount = useMutation({ mutationFn: async () => { const response = await fetch("/api/account", { method: "DELETE" }) - if (!response.ok) throw new Error("Failed to delete account") + if (!response.ok) throw new Error("failed to delete account") }, onSuccess: () => { router.push("/sign-in") @@ -112,6 +112,7 @@ export function DangerZoneSettings() { </p> {showDeleteAccountConfirm ? ( <div> + {/* eslint-disable-next-line asa-lowercase/lowercase-strings */} <p className="mb-2 text-status-error"> type DELETE to confirm account deletion. </p> @@ -120,7 +121,7 @@ export function DangerZoneSettings() { type="text" value={deleteConfirmText} onChange={(event) => setDeleteConfirmText(event.target.value)} - placeholder="type DELETE" + placeholder="type DELETE" // eslint-disable-line asa-lowercase/lowercase-strings className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none placeholder:text-text-dim focus:border-status-error" autoFocus /> diff --git a/apps/web/app/reader/settings/_components/import-export-settings.tsx b/apps/web/app/reader/settings/_components/import-export-settings.tsx index efb3f09..e84b56f 100644 --- a/apps/web/app/reader/settings/_components/import-export-settings.tsx +++ b/apps/web/app/reader/settings/_components/import-export-settings.tsx @@ -41,7 +41,7 @@ export function ImportExportSettings() { const groups = parseOpml(xmlString) setParsedGroups(groups) } catch { - notify("failed to parse OPML file") + notify("failed to parse opml file") } } reader.readAsText(file) @@ -99,7 +99,7 @@ export function ImportExportSettings() { setIsExportingData(true) try { const response = await fetch("/api/export") - if (!response.ok) throw new Error("Export failed") + if (!response.ok) throw new Error("export failed") const blob = await response.blob() const url = URL.createObjectURL(blob) const anchor = document.createElement("a") @@ -121,24 +121,24 @@ export function ImportExportSettings() { return ( <div className="px-4 py-3"> <div className="mb-6"> - <h3 className="mb-2 text-text-primary">export OPML</h3> + <h3 className="mb-2 text-text-primary">export opml</h3> <p className="mb-3 text-text-dim"> - download your subscriptions as an OPML file + download your subscriptions as an opml file </p> <button onClick={handleExport} disabled={!subscriptionsData} className="border border-border bg-background-tertiary px-4 py-2 text-text-primary transition-colors hover:bg-border disabled:opacity-50" > - export OPML + export opml </button> </div> <div className="mb-6"> <h3 className="mb-2 text-text-primary">export data</h3> <p className="mb-3 text-text-dim"> {tier === "pro" || tier === "developer" - ? "download all your data as JSON (subscriptions, folders, saved entries)" - : "download your saved entries as JSON (upgrade to pro for full export)"} + ? "download all your data as json (subscriptions, folders, saved entries)" + : "download your saved entries as json (upgrade to pro for full export)"} </p> <button onClick={handleDataExport} @@ -151,7 +151,7 @@ export function ImportExportSettings() { <div> <h3 className="mb-2 text-text-primary">import</h3> <p className="mb-3 text-text-dim"> - import subscriptions from an OPML file + import subscriptions from an opml file </p> {parsedGroups === null ? ( <div> @@ -166,7 +166,7 @@ export function ImportExportSettings() { onClick={() => fileInputReference.current?.click()} className="border border-border bg-background-tertiary px-4 py-2 text-text-primary transition-colors hover:bg-border" > - select OPML file + select opml file </button> </div> ) : ( diff --git a/apps/web/app/reader/settings/_components/security-settings.tsx b/apps/web/app/reader/settings/_components/security-settings.tsx index 4a00241..32c84c4 100644 --- a/apps/web/app/reader/settings/_components/security-settings.tsx +++ b/apps/web/app/reader/settings/_components/security-settings.tsx @@ -24,7 +24,7 @@ export function SecuritySettings() { const { data, error } = await supabaseClient.auth.mfa.listFactors() if (error) { - notify("failed to load MFA factors") + notify("failed to load mfa factors") setIsLoading(false) return } @@ -54,7 +54,7 @@ export function SecuritySettings() { setIsProcessing(false) if (error) { - notify("failed to start MFA enrolment: " + error.message) + notify("failed to start mfa enrolment: " + error.message) return } @@ -80,7 +80,7 @@ export function SecuritySettings() { if (challengeError) { setIsProcessing(false) - notify("failed to create MFA challenge: " + challengeError.message) + notify("failed to create mfa challenge: " + challengeError.message) return } @@ -147,7 +147,7 @@ export function SecuritySettings() { <div className="mb-6"> <h3 className="mb-2 text-text-primary">two-factor authentication</h3> <p className="mb-4 text-text-dim"> - add an extra layer of security to your account with a time-based one-time password (TOTP) authenticator app + add an extra layer of security to your account with a time-based one-time password (totp) authenticator app </p> {enrollmentState.step === "idle" && enrolledFactors.length === 0 && ( @@ -172,12 +172,12 @@ export function SecuritySettings() { {enrollmentState.step === "enrolling" && ( <div className="space-y-4"> <p className="text-text-secondary"> - scan this QR code with your authenticator app, then enter the 6-digit code below + scan this qr code with your authenticator app, then enter the 6-digit code below </p> <div className="inline-block bg-white p-4"> <img src={enrollmentState.qrCodeSvg} - alt="TOTP QR code" + alt="totp qr code" className="h-48 w-48" /> </div> @@ -234,7 +234,7 @@ export function SecuritySettings() { > <div> <span className="text-text-primary"> - {factor.friendly_name || "TOTP authenticator"} + {factor.friendly_name || "totp authenticator"} </span> <span className="ml-2 text-text-dim"> added{" "} diff --git a/apps/web/app/reader/settings/_components/settings-shell.tsx b/apps/web/app/reader/settings/_components/settings-shell.tsx index ae432f3..3c25281 100644 --- a/apps/web/app/reader/settings/_components/settings-shell.tsx +++ b/apps/web/app/reader/settings/_components/settings-shell.tsx @@ -23,7 +23,7 @@ const TABS = [ { key: "account", label: "account" }, { key: "security", label: "security" }, { key: "billing", label: "billing" }, - { key: "api", label: "API" }, + { key: "api", label: "api" }, { key: "danger", label: "danger zone" }, ] as const diff --git a/apps/web/app/reader/shares/_components/shares-content.tsx b/apps/web/app/reader/shares/_components/shares-content.tsx index e9ce7a4..b05d562 100644 --- a/apps/web/app/reader/shares/_components/shares-content.tsx +++ b/apps/web/app/reader/shares/_components/shares-content.tsx @@ -91,7 +91,7 @@ function useUpdateShareNote() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ note }), }) - if (!response.ok) throw new Error("Failed to update note") + if (!response.ok) throw new Error("failed to update note") }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["shared-entries"] }) diff --git a/apps/web/eslint-rules/lowercase-strings.mjs b/apps/web/eslint-rules/lowercase-strings.mjs new file mode 100644 index 0000000..096d236 --- /dev/null +++ b/apps/web/eslint-rules/lowercase-strings.mjs @@ -0,0 +1,262 @@ +const UPPERCASE_PATTERN = /\b[A-Z]/ + +const IGNORED_JSX_PARENTS = new Set(["code", "Code"]) + +const CHECKED_JSX_ATTRIBUTES = new Set(["placeholder", "alt", "title"]) + +const NOTIFY_NAMES = new Set(["notify"]) + +function isInsideCodeElement(node) { + let current = node.parent + while (current) { + if ( + current.type === "JSXElement" && + current.openingElement?.name?.name && + IGNORED_JSX_PARENTS.has(current.openingElement.name.name) + ) { + return true + } + current = current.parent + } + return false +} + +function isClassName(node) { + return ( + node.parent?.type === "JSXAttribute" && + node.parent.name?.name === "className" + ) +} + +function isHrefOrSrc(node) { + return ( + node.parent?.type === "JSXAttribute" && + (node.parent.name?.name === "href" || node.parent.name?.name === "src") + ) +} + +function isFetchMethod(node) { + if (node.parent?.type !== "Property") return false + const key = node.parent.key + return key?.type === "Identifier" && key.name === "method" +} + +function isObjectKey(node) { + return ( + node.parent?.type === "Property" && + node.parent.key === node + ) +} + +function isImportSource(node) { + return ( + node.parent?.type === "ImportDeclaration" && + node.parent.source === node + ) +} + +function isTypeContext(node) { + return ( + node.parent?.type === "TSTypeReference" || + node.parent?.type === "TSLiteralType" || + node.parent?.type === "TSPropertySignature" + ) +} + +function isQueryKey(node) { + return ( + node.parent?.type === "ArrayExpression" && + node.parent.parent?.type === "Property" && + node.parent.parent.key?.name === "queryKey" + ) +} + +function isContentTypeHeader(node) { + if (node.parent?.type !== "Property") return false + const key = node.parent.key + return key?.type === "Literal" && key.value === "Content-Type" +} + +function isHeaderValue(node) { + if (node.parent?.type !== "Property") return false + const key = node.parent.key + if (key?.type === "Literal") { + const keyValue = String(key.value).toLowerCase() + return keyValue === "content-type" || keyValue === "authorization" + } + return false +} + +function isComparisonTarget(node) { + return ( + node.parent?.type === "BinaryExpression" && + (node.parent.operator === "===" || node.parent.operator === "!==") && + node.parent.right === node + ) +} + +function looksLikeCode(value) { + if (value.startsWith("Bearer ")) return true + if (value.startsWith("Authorization:")) return true + if (/^https?:\/\//.test(value)) return true + if (value.includes("::")) return true + return false +} + +const rule = { + meta: { + type: "suggestion", + docs: { + description: "enforce lowercase user-facing strings", + }, + messages: { + uppercaseString: + "user-facing string contains uppercase: \"{{value}}\". all user-facing text must be lowercase.", + uppercaseJsxText: + "jsx text contains uppercase: \"{{value}}\". all user-facing text must be lowercase.", + }, + schema: [], + }, + create(context) { + function checkStringLiteral(node, value, messageId) { + if (!UPPERCASE_PATTERN.test(value)) return + if (looksLikeCode(value)) return + + context.report({ + node, + messageId, + data: { + value: value.length > 60 ? value.slice(0, 57) + "..." : value, + }, + }) + } + + return { + JSXText(node) { + const trimmed = node.value.trim() + if (!trimmed) return + if (!UPPERCASE_PATTERN.test(trimmed)) return + if (isInsideCodeElement(node)) return + + context.report({ + node, + messageId: "uppercaseJsxText", + data: { + value: + trimmed.length > 60 ? trimmed.slice(0, 57) + "..." : trimmed, + }, + }) + }, + + Literal(node) { + if (typeof node.value !== "string") return + if (!UPPERCASE_PATTERN.test(node.value)) return + + if (isInsideCodeElement(node)) return + if (isClassName(node)) return + if (isHrefOrSrc(node)) return + if (isFetchMethod(node)) return + if (isObjectKey(node)) return + if (isImportSource(node)) return + if (isTypeContext(node)) return + if (isQueryKey(node)) return + if (isContentTypeHeader(node)) return + if (isHeaderValue(node)) return + if (isComparisonTarget(node)) return + + if ( + node.parent?.type === "CallExpression" && + node.parent.callee?.type === "Identifier" && + NOTIFY_NAMES.has(node.parent.callee.name) + ) { + checkStringLiteral(node, node.value, "uppercaseString") + return + } + + if ( + node.parent?.type === "NewExpression" && + node.parent.callee?.type === "Identifier" && + node.parent.callee.name === "Error" + ) { + checkStringLiteral(node, node.value, "uppercaseString") + return + } + + if ( + node.parent?.type === "JSXAttribute" && + CHECKED_JSX_ATTRIBUTES.has(node.parent.name?.name) + ) { + checkStringLiteral(node, node.value, "uppercaseString") + return + } + + if (node.parent?.type === "JSXExpressionContainer") { + if (isInsideCodeElement(node.parent)) return + checkStringLiteral(node, node.value, "uppercaseString") + return + } + + if ( + node.parent?.type === "Property" && + node.parent.value === node && + node.parent.key?.type === "Identifier" + ) { + const keyName = node.parent.key.name + if ( + keyName === "label" || + keyName === "description" || + keyName === "title" || + keyName === "error" + ) { + checkStringLiteral(node, node.value, "uppercaseString") + return + } + } + }, + + TemplateLiteral(node) { + const isNotifyArg = + node.parent?.type === "CallExpression" && + node.parent.callee?.type === "Identifier" && + NOTIFY_NAMES.has(node.parent.callee.name) + + const isErrorArg = + node.parent?.type === "NewExpression" && + node.parent.callee?.type === "Identifier" && + node.parent.callee.name === "Error" + + const isJsxExpression = + node.parent?.type === "JSXExpressionContainer" + + if (!isNotifyArg && !isErrorArg && !isJsxExpression) return + if (isInsideCodeElement(node)) return + + for (const quasi of node.quasis) { + const raw = quasi.value.raw + if (UPPERCASE_PATTERN.test(raw) && !looksLikeCode(raw)) { + context.report({ + node: quasi, + messageId: "uppercaseString", + data: { + value: + raw.length > 60 ? raw.slice(0, 57) + "..." : raw, + }, + }) + } + } + }, + } + }, +} + +const plugin = { + meta: { + name: "asa-lowercase", + version: "1.0.0", + }, + rules: { + "lowercase-strings": rule, + }, +} + +export default plugin diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs index 05e726d..0ca21d3 100644 --- a/apps/web/eslint.config.mjs +++ b/apps/web/eslint.config.mjs @@ -1,18 +1,25 @@ import { defineConfig, globalIgnores } from "eslint/config"; import nextVitals from "eslint-config-next/core-web-vitals"; import nextTs from "eslint-config-next/typescript"; +import asaLowercase from "./eslint-rules/lowercase-strings.mjs"; const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, - // Override default ignores of eslint-config-next. globalIgnores([ - // Default ignores of eslint-config-next: ".next/**", "out/**", "build/**", "next-env.d.ts", ]), + { + plugins: { + "asa-lowercase": asaLowercase, + }, + rules: { + "asa-lowercase/lowercase-strings": "warn", + }, + }, ]); export default eslintConfig; diff --git a/apps/web/lib/api-auth.ts b/apps/web/lib/api-auth.ts index 309fbe9..3f819f2 100644 --- a/apps/web/lib/api-auth.ts +++ b/apps/web/lib/api-auth.ts @@ -19,14 +19,14 @@ export async function authenticateApiRequest( return { authenticated: false, status: 401, - error: "Missing or invalid Authorization header", + error: "missing or invalid authorization header", } } const apiKey = authorizationHeader.slice(7) if (!apiKey.startsWith("asn_")) { - return { authenticated: false, status: 401, error: "Invalid API key format" } + return { authenticated: false, status: 401, error: "invalid api key format" } } const keyHash = hashApiKey(apiKey) @@ -40,7 +40,7 @@ export async function authenticateApiRequest( .single() if (!keyRow) { - return { authenticated: false, status: 401, error: "Invalid or revoked API key" } + return { authenticated: false, status: 401, error: "invalid or revoked api key" } } const { data: userProfile } = await adminClient @@ -53,7 +53,7 @@ export async function authenticateApiRequest( return { authenticated: false, status: 403, - error: "API access requires the developer plan", + error: "api access requires the developer plan", } } @@ -63,7 +63,7 @@ export async function authenticateApiRequest( return { authenticated: false, status: 429, - error: `Rate limit exceeded. ${rateLimitResult.remaining} requests remaining.`, + error: `rate limit exceeded. ${rateLimitResult.remaining} requests remaining.`, } } diff --git a/apps/web/lib/opml.ts b/apps/web/lib/opml.ts index bd0c3a7..5c0e3e2 100644 --- a/apps/web/lib/opml.ts +++ b/apps/web/lib/opml.ts @@ -87,13 +87,13 @@ export function parseOpml(xmlString: string): ParsedOpmlGroup[] { const parseError = document.querySelector("parsererror") if (parseError) { - throw new Error("Invalid OPML file") + throw new Error("invalid opml file") } const body = document.querySelector("body") if (!body) { - throw new Error("Invalid OPML: no body element") + throw new Error("invalid opml: no body element") } const groups: ParsedOpmlGroup[] = [] diff --git a/apps/web/lib/queries/use-custom-feed-mutations.ts b/apps/web/lib/queries/use-custom-feed-mutations.ts index f0751db..ad6b328 100644 --- a/apps/web/lib/queries/use-custom-feed-mutations.ts +++ b/apps/web/lib/queries/use-custom-feed-mutations.ts @@ -25,7 +25,7 @@ export function useCreateCustomFeed() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient.from("custom_feeds").insert({ user_id: user.id, diff --git a/apps/web/lib/queries/use-custom-feeds.ts b/apps/web/lib/queries/use-custom-feeds.ts index 5c11721..a93e431 100644 --- a/apps/web/lib/queries/use-custom-feeds.ts +++ b/apps/web/lib/queries/use-custom-feeds.ts @@ -24,7 +24,7 @@ export function useCustomFeeds() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { data, error } = await supabaseClient .from("custom_feeds") diff --git a/apps/web/lib/queries/use-entry-state-mutations.ts b/apps/web/lib/queries/use-entry-state-mutations.ts index 5f79fc0..a8c72d0 100644 --- a/apps/web/lib/queries/use-entry-state-mutations.ts +++ b/apps/web/lib/queries/use-entry-state-mutations.ts @@ -22,7 +22,7 @@ export function useToggleEntryReadState() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient .from("user_entry_states") @@ -88,7 +88,7 @@ export function useToggleEntrySavedState() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient .from("user_entry_states") diff --git a/apps/web/lib/queries/use-folder-mutations.ts b/apps/web/lib/queries/use-folder-mutations.ts index 8595a60..642bd96 100644 --- a/apps/web/lib/queries/use-folder-mutations.ts +++ b/apps/web/lib/queries/use-folder-mutations.ts @@ -15,7 +15,7 @@ export function useCreateFolder() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient.from("folders").insert({ user_id: user.id, @@ -48,7 +48,7 @@ export function useDeleteAllFolders() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") await supabaseClient .from("subscriptions") diff --git a/apps/web/lib/queries/use-highlight-mutations.ts b/apps/web/lib/queries/use-highlight-mutations.ts index 0e228c8..6c9dbb7 100644 --- a/apps/web/lib/queries/use-highlight-mutations.ts +++ b/apps/web/lib/queries/use-highlight-mutations.ts @@ -26,7 +26,7 @@ export function useCreateHighlight() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { data, error } = await supabaseClient .from("user_highlights") diff --git a/apps/web/lib/queries/use-muted-keyword-mutations.ts b/apps/web/lib/queries/use-muted-keyword-mutations.ts index 67bcf33..de4e03f 100644 --- a/apps/web/lib/queries/use-muted-keyword-mutations.ts +++ b/apps/web/lib/queries/use-muted-keyword-mutations.ts @@ -15,7 +15,7 @@ export function useAddMutedKeyword() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient.from("muted_keywords").insert({ user_id: user.id, diff --git a/apps/web/lib/queries/use-subscription-mutations.ts b/apps/web/lib/queries/use-subscription-mutations.ts index 3b4b3ba..3162d96 100644 --- a/apps/web/lib/queries/use-subscription-mutations.ts +++ b/apps/web/lib/queries/use-subscription-mutations.ts @@ -109,7 +109,7 @@ export function useUnsubscribeAll() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { error } = await supabaseClient .from("subscriptions") diff --git a/apps/web/lib/queries/use-user-profile.ts b/apps/web/lib/queries/use-user-profile.ts index 760f970..49c1c4e 100644 --- a/apps/web/lib/queries/use-user-profile.ts +++ b/apps/web/lib/queries/use-user-profile.ts @@ -15,7 +15,7 @@ export function useUserProfile() { data: { user }, } = await supabaseClient.auth.getUser() - if (!user) throw new Error("Not authenticated") + if (!user) throw new Error("not authenticated") const { data, error } = await supabaseClient .from("user_profiles") |