Skip to content

Preserve channel owner on channel upsert#1734

Merged
riderx merged 7 commits intomainfrom
riderx/channel-post-fix
Mar 3, 2026
Merged

Preserve channel owner on channel upsert#1734
riderx merged 7 commits intomainfrom
riderx/channel-post-fix

Conversation

@riderx
Copy link
Member

@riderx riderx commented Mar 2, 2026

Summary (AI generated)

This change prevents POST /channel from overwriting immutable ownership on existing channel updates by preserving the original created_by when a channel with the same app_id and name already exists.

Test plan (AI generated)

I ran bunx eslint supabase/functions/_backend/utils/supabase.ts and verified no lint errors.

Screenshots (AI generated)

No screenshots are needed because this is backend-only.

Checklist (AI generated)

  • My code follows the project style and passes bun run lint:backend && bun run lint.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • My change has adequate E2E test coverage.
  • I have tested my code manually, and I have provided steps to reproduce my tests.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed channel update functionality to properly handle creator attribution, allowing channels to be successfully modified while preserving information about the original creator. Enhanced logic ensures updates work reliably in various scenarios and maintains system data consistency.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Warning

Rate limit exceeded

@riderx has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between e53b33e and 9a623b7.

📒 Files selected for processing (9)
  • messages/en.json
  • src/modules/auth.ts
  • src/modules/i18n.ts
  • src/pages/delete_account.vue
  • src/pages/resend_email.vue
  • supabase/migrations/20260225120000_restrict_webhooks_select_for_admin_only.sql
  • supabase/migrations/20260226090000_require_verified_email_for_delete_user.sql
  • supabase/migrations/20260227000000_secure_record_build_time_rpc.sql
  • supabase/migrations/20260227150000_fix_invite_user_to_org_security.sql
📝 Walkthrough

Walkthrough

The updateOrCreateChannel function in the Supabase utilities module was modified to preserve an existing channel's created_by field during updates. The lookup query and payload construction now handle cases where created_by differs between the update request and stored channel data.

Changes

Cohort / File(s) Summary
Channel Upsert Logic
supabase/functions/_backend/utils/supabase.ts
Modified updateOrCreateChannel to preserve the existing channel's created_by field during updates, or use the provided value if no channel exists. Updated lookup logic and field-difference checks to work with the new upsert payload construction.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A channel's creator, preserved with care,
When updates arrive, the old keeper stays there,
Unless none existed, then new hands take hold—
Creator stories, forever untold! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: preserving the channel owner during an upsert operation.
Description check ✅ Passed The description follows the template structure with Summary, Test plan, Screenshots, and Checklist sections. All required sections are present with substantive content.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch riderx/channel-post-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/functions/_backend/utils/supabase.ts (1)

138-149: ⚠️ Potential issue | 🟠 Major

Handle lookup errors before building upsertPayload.

At Line 138–143, lookup errors are ignored. If that query fails, Line 147 falls back to update.created_by, and the upsert can overwrite ownership despite this PR’s intent.

🔧 Proposed fix
-  const { data: existingChannel } = await supabaseAdmin(c)
+  const { data: existingChannel, error: existingChannelError } = await supabaseAdmin(c)
     .from('channels')
     .select('*')
     .eq('app_id', update.app_id)
     .eq('name', update.name)
-    .single()
+    .maybeSingle()
+
+  if (existingChannelError) {
+    cloudlogErr({ requestId: c.get('requestId'), message: 'channel lookup failed', error: existingChannelError })
+    return Promise.reject(existingChannelError)
+  }

   const upsertPayload = {
     ...update,
-    created_by: existingChannel?.created_by || update.created_by,
+    created_by: existingChannel?.created_by ?? update.created_by,
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/utils/supabase.ts` around lines 138 - 149, The
lookup result from supabaseAdmin(c).from('channels').select(...).single() may
return an error which is currently ignored before constructing upsertPayload;
update.created_by should not silently override existing ownership when the
lookup failed. After the query, check the returned error (the variable paired
with data) and if present, surface/propagate it (throw, return an error
response, or log and abort) instead of proceeding to build upsertPayload; then
only set created_by from existingChannel?.created_by when existingChannel is
present, otherwise fail the operation so upsertPayload cannot overwrite
ownership inadvertently (refer to supabaseAdmin, existingChannel, upsertPayload,
and update.created_by).
🧹 Nitpick comments (1)
supabase/functions/_backend/utils/supabase.ts (1)

160-163: Migrate this DB write path to the PG/Drizzle client helpers.

This changed upsert path still goes through supabaseAdmin(c).

As per coding guidelines "All database operations must use getPgClient() or getDrizzleClient() from utils/pg.ts for PostgreSQL access during active migration to Cloudflare D1".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/_backend/utils/supabase.ts` around lines 160 - 163, The
current upsert to the channels table uses supabaseAdmin(c); replace it with the
PG/Drizzle client helpers by obtaining a client via getDrizzleClient(c) (or
getPgClient(c) if preferred) and perform the equivalent upsert of upsertPayload
into the channels table using Drizzle's insert().onConflict().doUpdate() (or the
corresponding upsert helper on the pg client), ensuring the conflict target is
app_id and name and preserving the same values as the original upsert, and
remove the call to throwOnError in favor of proper error handling from the
Drizzle/PG client.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@supabase/functions/_backend/utils/supabase.ts`:
- Around line 138-149: The lookup result from
supabaseAdmin(c).from('channels').select(...).single() may return an error which
is currently ignored before constructing upsertPayload; update.created_by should
not silently override existing ownership when the lookup failed. After the
query, check the returned error (the variable paired with data) and if present,
surface/propagate it (throw, return an error response, or log and abort) instead
of proceeding to build upsertPayload; then only set created_by from
existingChannel?.created_by when existingChannel is present, otherwise fail the
operation so upsertPayload cannot overwrite ownership inadvertently (refer to
supabaseAdmin, existingChannel, upsertPayload, and update.created_by).

---

Nitpick comments:
In `@supabase/functions/_backend/utils/supabase.ts`:
- Around line 160-163: The current upsert to the channels table uses
supabaseAdmin(c); replace it with the PG/Drizzle client helpers by obtaining a
client via getDrizzleClient(c) (or getPgClient(c) if preferred) and perform the
equivalent upsert of upsertPayload into the channels table using Drizzle's
insert().onConflict().doUpdate() (or the corresponding upsert helper on the pg
client), ensuring the conflict target is app_id and name and preserving the same
values as the original upsert, and remove the call to throwOnError in favor of
proper error handling from the Drizzle/PG client.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between effb8b6 and e53b33e.

📒 Files selected for processing (1)
  • supabase/functions/_backend/utils/supabase.ts

riderx added 6 commits March 3, 2026 12:02
* fix(auth): block account deletion for unverified users

* fix(auth): refresh session fields for email verification gate

* fix(auth): make delete_user insert idempotent

* fix(auth): explain blocked delete/settings when email unverified

* fix(auth): block delete action when email is unverified

* fix(auth): localize resend email block and make delete_user idempotent
* fix(db): restrict invite_user_to_org public rpc

* fix(db): use caller identity in invite 2FA check
* fix(db): secure record_build_time rpc writes

* fix(db): preserve service-role record_build_time path
@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 3, 2026

@riderx riderx merged commit 60b278b into main Mar 3, 2026
15 checks passed
@riderx riderx deleted the riderx/channel-post-fix branch March 3, 2026 12:50
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.

1 participant