Skip to content

fix: replace .single() with .maybeSingle() in 5 more routes (PR 3/3)#477

Closed
tankgxy wants to merge 1 commit into
profullstack:masterfrom
tankgxy:fix/even-more-single
Closed

fix: replace .single() with .maybeSingle() in 5 more routes (PR 3/3)#477
tankgxy wants to merge 1 commit into
profullstack:masterfrom
tankgxy:fix/even-more-single

Conversation

@tankgxy

@tankgxy tankgxy commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Subscriptions, testimonials, verification, video-calls, wallet/zap routes.

@greptile-apps

greptile-apps Bot commented Jun 14, 2026

Copy link
Copy Markdown

Greptile Summary

This is the third in a series of PRs migrating Supabase queries from .single() to .maybeSingle() across subscriptions, testimonials, verification, video-calls, and wallet/zap routes. The intent is correct — .maybeSingle() avoids the PGRST116 error when no row exists — but the patch introduces two separate problems alongside the migration.

  • Encoding corruption: Several emoji and em-dash characters (, , , ) were corrupted to replacement bytes during the edit. In testimonials/route.ts this breaks the stars rating string used in every notification email; in wallet/zap/route.ts it corrupts the title of every in-app zap notification.
  • Null dereference in video-calls: After switching the video_calls insert to .maybeSingle(), call can be null yet call.id is accessed unconditionally at two sites (lines 165 and 202), creating a crash path if the inserted row cannot be read back.
  • Dead guard in subscriptions: The error.code !== \"PGRST116\" check in GET /api/subscriptions is now unreachable dead code because maybeSingle() never produces that error code.

Confidence Score: 3/5

Not safe to merge as-is — testimonial emails and zap notifications will display corrupted characters, and a crash path exists in the video-call creation flow.

The encoding corruption in testimonials/route.ts breaks every testimonial email (star glyphs → garbage), the same corruption in wallet/zap/route.ts breaks every in-app zap notification title, and the unguarded call.id access in video-calls/route.ts will throw at runtime whenever Supabase returns null from the insert's maybeSingle(). Three distinct defects across three of the five changed files make this risky to ship without fixes.

src/app/api/testimonials/route.ts, src/app/api/wallet/zap/route.ts, and src/app/api/video-calls/route.ts all need attention before merging.

Important Files Changed

Filename Overview
src/app/api/subscriptions/route.ts Three .single().maybeSingle() replacements; logic is correct but the now-dead PGRST116 error-code guard in GET should be cleaned up.
src/app/api/testimonials/route.ts Four .single().maybeSingle() changes; also introduces character-encoding corruption that destroys the / star glyphs in the stars variable and the em-dash in the email subject — user-visible breakage in every testimonial notification email.
src/app/api/verification/request/route.ts Three .single().maybeSingle() replacements; the em-dash corruption appears only in a comment so there is no functional impact.
src/app/api/video-calls/route.ts Four .single().maybeSingle() changes; the insert result call is now potentially null but call.id is accessed unconditionally at lines 165 and 202, creating a crash path when the inserted row cannot be read back.
src/app/api/wallet/zap/route.ts Three .single().maybeSingle() changes; the ⚡ emoji in both notification title strings is corrupted to a replacement character, breaking in-app zap notifications for all recipients.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Route as API Route
    participant Supabase

    note over Route,Supabase: Before PR — .single()
    Client->>Route: Request
    Route->>Supabase: query.single()
    alt 0 rows
        Supabase-->>Route: error (PGRST116)
        Route-->>Client: 400 / unhandled throw
    else 1 row
        Supabase-->>Route: data row
        Route-->>Client: 200 OK
    end

    note over Route,Supabase: After PR — .maybeSingle()
    Client->>Route: Request
    Route->>Supabase: query.maybeSingle()
    alt 0 rows
        Supabase-->>Route: "data = null, error = null"
        Route-->>Client: handled gracefully (null check)
    else 1 row
        Supabase-->>Route: data row
        Route-->>Client: 200 OK
    else insert + RLS blocks read-back (video-calls)
        Supabase-->>Route: "data = null, error = null"
        Route->>Route: call.id → TypeError (crash)
        Route-->>Client: 500 Error
    end
Loading

Comments Outside Diff (2)

  1. src/app/api/video-calls/route.ts, line 150-165 (link)

    P1 Null dereference on call.id after switching to maybeSingle()

    With .maybeSingle(), call can be null even when no createError is present — for example, if the row-level security policy on video_calls prevents reading back the just-inserted row. Line 165 accesses call.id unconditionally, which will throw a TypeError: Cannot read properties of null that propagates as a generic 500 response. The same call.id is passed again at line 202 inside videoCallInviteEmail. A guard like if (!call) after the error check would prevent this crash.

  2. src/app/api/subscriptions/route.ts, line 20-22 (link)

    P2 Dead PGRST116 guard after maybeSingle() migration

    PGRST116 is the error Supabase returns when .single() finds zero rows. Since the query now uses .maybeSingle(), that error code will never appear in error.code — a zero-row result simply returns null data without an error. The error.code !== "PGRST116" condition is now always true when error is truthy, making the guard redundant. The check can safely be simplified to if (error).

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (1): Last reviewed commit: "fix: replace .single() with .maybeSingle..." | Re-trigger Greptile


const authorName = authorProfile?.full_name || authorProfile?.username || "Someone";
const stars = "★".repeat(rating) + "☆".repeat(5 - rating);
const stars = "�?.repeat(rating) + "�?.repeat(5 - rating);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Corrupted emoji characters in stars variable

The and characters have been replaced with garbled/replacement bytes (?). As written, the stars variable will contain corrupted characters repeated rating times rather than actual star glyphs. This string appears in the HTML email body (line 230) and the email subject (line 227), so every testimonial notification email will show garbage characters instead of a star rating like ★★★☆☆.

Comment on lines +246 to 255
title: "You received a zap! �?,
body: `${senderName} zapped you ${recipient_amount.toLocaleString()} sats`,
data: { zap_id: zapId, amount_sats: recipient_amount, target_type, target_id },
});
} else {
await (admin.from("notifications") as any).insert({
user_id: recipient_id,
type: "zap_received",
title: "You received a zap! ⚡",
title: "You received a zap! �?,
body: `${senderName} zapped you ${recipient_amount.toLocaleString()} sats. Add a Lightning Address to your profile to withdraw.`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Corrupted ⚡ emoji in notification titles

The lightning bolt emoji in both "You received a zap! ⚡" notification title strings has been replaced with garbled replacement bytes. Every zap notification sent to recipients (both the ln_address and non-ln_address branches) will display a corrupted character instead of the emoji. The title field is user-visible in the in-app notification UI.

@ralyodio ralyodio closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants