Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Oct 26, 2025

Summary by CodeRabbit

  • New Features
    • Partner profile change history: admins can view a persisted log of country and profile-type updates.
    • Automatic Stripe Connect disconnection when country or profile type changes to keep payment data consistent.
    • Improved update rules: profile changes are now validated and blocked or allowed more accurately when payouts are enabled.

@vercel
Copy link
Contributor

vercel bot commented Oct 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Oct 26, 2025 11:42pm

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

Walkthrough

Adds partner profile change history tracking: new Zod schema, a JSON changeHistoryLog field on the Partner model, and updates to the partner update and onboarding actions to record country/profileType changes; when those fields change and payouts are enabled, the code deletes the Stripe Connect account and clears stripeConnectId before persisting updates.

Changes

Cohort / File(s) Summary
Zod schema
apps/web/lib/zod/schemas/partner-profile.ts
Adds partnerProfileChangeHistoryLogSchema export: a Zod array union describing country and profileType change entries with from/to values and changedAt timestamps.
Prisma schema
packages/prisma/schema/partner.prisma
Adds an optional changeHistoryLog Json? field to the Partner model to persist change history entries.
Partner update action
apps/web/lib/actions/partners/update-partner-profile.ts
Detects country/profileType changes, appends entries to a changeHistoryLog, returns early if neither changed, reworks compliance-check branching, deletes existing Stripe Connect account when relevant and payouts are enabled, clears stripeConnectId, and updates the DB with the new changeHistoryLog.
Partner onboarding action
apps/web/lib/actions/partners/onboard-partner.ts
Adjusts conditional update logic to base field updates on existing partner country and profileType rather than stripeConnectId; removes companyName from that conditional payload and stops assigning the Promise.all result to a variable.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant A as update-partner-profile
    participant DB as Database
    participant S as Stripe API

    U->>A: Submit partner update (country/profileType)
    A->>A: Compare incoming vs stored country/profileType
    alt No country/profileType change
        A->>DB: Update other fields (no history, keep stripeConnectId)
        DB-->>A: OK
    else country/profileType changed
        A->>A: Append change entries to changeHistoryLog (from/to + changedAt)
        alt Payouts enabled
            A->>S: Delete Stripe Connect account (if exists)
            S-->>A: Deleted/OK
            A->>DB: Update partner: set stripeConnectId = null + persist changeHistoryLog
        else Payouts not enabled
            A->>DB: Update partner: persist changeHistoryLog (stripeConnectId unchanged)
        end
        DB-->>A: Updated
    end
    A-->>U: Response (updated partner)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • The early-return change vs. previous compliance-blocking behavior in updatedComplianceFieldsChecks.
    • Atomicity and error handling around Stripe account deletion followed by DB update.
    • Correct serialization/typing of changeHistoryLog between Zod schema and Prisma JSON field.
    • Accuracy of recorded from/to values and timestamp generation.

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐰 I hopped through fields both new and old,
I logged each change in ink and gold.
When countries shift or roles restart,
I un-tether Stripe and mend the chart.
A tiny hop for safer heart. 🥕

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Add changeHistoryLog" is concise and directly related to the changeset. The PR introduces a new changeHistoryLog field to the Partner model, adds a corresponding Zod validation schema (partnerProfileChangeHistoryLogSchema), and implements history tracking logic in the update-partner-profile action to record changes to country and profileType. The title accurately captures this central addition and is specific enough for a teammate to understand the primary feature being added, without needing to cover every detail like the related Stripe integration improvements that support this feature.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch change-history-log

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c637fd1 and ffd958e.

📒 Files selected for processing (3)
  • apps/web/lib/actions/partners/onboard-partner.ts (2 hunks)
  • apps/web/lib/actions/partners/update-partner-profile.ts (2 hunks)
  • apps/web/lib/zod/schemas/partner-profile.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/lib/zod/schemas/partner-profile.ts
  • apps/web/lib/actions/partners/update-partner-profile.ts
🔇 Additional comments (2)
apps/web/lib/actions/partners/onboard-partner.ts (2)

43-45: Verify the UX when users re-onboard with different field values.

The logic change from checking stripeConnectId to checking field presence is more direct and appropriate. However, if an existing partner attempts to onboard again with different country or profileType values, those values are silently ignored without any feedback.

Consider whether the user should receive feedback when their submitted values differ from existing immutable fields. You could add validation in the schema or return a warning message to improve the user experience.


67-67: LGTM – unused return value removed.

The removal of the unused partner result from Promise.all is appropriate cleanup.


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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 26, 2025

✅ Actions performed

Full review triggered.

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web/lib/zod/schemas/partner-profile.ts (1)

177-192: Use discriminatedUnion and strict objects for faster, safer parsing.

Current union works; a discriminated union on field improves inference and performance and .strict() prevents stray keys. Optional: default to [].

Apply:

-export const partnerProfileChangeHistoryLogSchema = z.array(
-  z.union([
-    z.object({
-      field: z.literal("country"),
-      from: z.string().nullable(),
-      to: z.string().nullable(),
-      changedAt: z.coerce.date(),
-    }),
-    z.object({
-      field: z.literal("profileType"),
-      from: z.nativeEnum(PartnerProfileType),
-      to: z.nativeEnum(PartnerProfileType),
-      changedAt: z.coerce.date(),
-    }),
-  ]),
-);
+export const partnerProfileChangeHistoryLogSchema = z
+  .array(
+    z.discriminatedUnion("field", [
+      z
+        .object({
+          field: z.literal("country"),
+          from: z.string().nullable(),
+          to: z.string().nullable(),
+          changedAt: z.coerce.date(),
+        })
+        .strict(),
+      z
+        .object({
+          field: z.literal("profileType"),
+          from: z.nativeEnum(PartnerProfileType),
+          to: z.nativeEnum(PartnerProfileType),
+          changedAt: z.coerce.date(),
+        })
+        .strict(),
+    ]),
+  )
+  .default([]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a754ea9 and c637fd1.

📒 Files selected for processing (3)
  • apps/web/lib/actions/partners/update-partner-profile.ts (2 hunks)
  • apps/web/lib/zod/schemas/partner-profile.ts (2 hunks)
  • packages/prisma/schema/partner.prisma (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/lib/zod/schemas/partner-profile.ts (1)
packages/prisma/client.ts (1)
  • PartnerProfileType (22-22)
apps/web/lib/actions/partners/update-partner-profile.ts (3)
apps/web/lib/zod/schemas/partner-profile.ts (1)
  • partnerProfileChangeHistoryLogSchema (177-192)
apps/web/lib/stripe/index.ts (1)
  • stripe (4-10)
packages/prisma/index.ts (1)
  • prisma (3-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
apps/web/lib/zod/schemas/partner-profile.ts (1)

7-7: LGTM on import.

apps/web/lib/actions/partners/update-partner-profile.ts (1)

8-9: LGTM on new imports.

packages/prisma/schema/partner.prisma (1)

61-61: Verify db push is used for schema deployment.

The schema field changeHistoryLog Json? is defined and will be deployed via prisma db push (no separate migration file needed in this project's architecture). Code properly validates and uses it with partnerProfileChangeHistoryLogSchema, initializing as empty array for existing partners. No backfill script required since the field is optional.

However, confirm the schema change will be applied by running pnpm run push in the prisma package before deployment. The architectural suggestions in the original comment (dedicated table vs JSON, retention caps) remain valid considerations for future scale.

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.

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/web/lib/actions/partners/update-partner-profile.ts (2)

279-282: Parse defensively to avoid crashes on malformed JSON.

Switch to safeParse with fallback to [] to avoid throwing if the stored JSON is malformed. This was already suggested in earlier feedback.


283-290: Null-safe assignment for country “to”.

Use input.country ?? null instead of a string assertion. This matches the schema’s .nullable() and avoids incorrect typing. Previously noted; echoing for completeness.

-      to: input.country as string,
+      to: input.country ?? null,
🧹 Nitpick comments (3)
packages/prisma/schema/partner.prisma (1)

61-61: Consider defaulting JSON and/or making it non-null.

To reduce null checks and parsing pitfalls, consider storing an empty array by default and making this non-null (DB default jsonb '[]', then NOT NULL). If you prefer keeping it nullable, ensure all reads use safeParse with fallback.

apps/web/lib/zod/schemas/partner-profile.ts (1)

177-192: Use discriminated union and export the type.

Improves inference and narrowing; keeps shape explicit.

-export const partnerProfileChangeHistoryLogSchema = z.array(
-  z.union([
-    z.object({
-      field: z.literal("country"),
-      from: z.string().nullable(),
-      to: z.string().nullable(),
-      changedAt: z.coerce.date(),
-    }),
-    z.object({
-      field: z.literal("profileType"),
-      from: z.nativeEnum(PartnerProfileType),
-      to: z.nativeEnum(PartnerProfileType),
-      changedAt: z.coerce.date(),
-    }),
-  ]),
-);
+export const partnerProfileChangeHistoryLogSchema = z.array(
+  z.discriminatedUnion("field", [
+    z.object({
+      field: z.literal("country"),
+      from: z.string().nullable(),
+      to: z.string().nullable(),
+      changedAt: z.coerce.date(),
+    }),
+    z.object({
+      field: z.literal("profileType"),
+      from: z.nativeEnum(PartnerProfileType),
+      to: z.nativeEnum(PartnerProfileType),
+      changedAt: z.coerce.date(),
+    }),
+  ]),
+);
+
+export type PartnerProfileChangeHistoryLog = z.infer<
+  typeof partnerProfileChangeHistoryLogSchema
+>;
apps/web/lib/actions/partners/update-partner-profile.ts (1)

301-314: Reorder DB update before Stripe deletion; make deletion idempotent and async.

Avoid inconsistent audit/state if the later update fails, and don’t fail the entire action on already-deleted accounts.

-  if (partner.stripeConnectId) {
-    await stripe.accounts.del(partner.stripeConnectId);
-  }
-
-  await prisma.partner.update({
+  // First persist app state + history
+  await prisma.partner.update({
     where: {
       id: partner.id,
     },
     data: {
       stripeConnectId: null,
       changeHistoryLog: partnerChangeHistoryLog,
     },
   });
+
+  // Then delete the Stripe account best-effort (idempotent)
+  if (partner.stripeConnectId) {
+    waitUntil(
+      (async () => {
+        try {
+          await stripe.accounts.del(partner.stripeConnectId!);
+        } catch (err: any) {
+          // Treat not found as success; rethrow others
+          const code = err?.statusCode ?? err?.raw?.statusCode;
+          if (code !== 404) throw err;
+        }
+      })(),
+    );
+  }

Optional: cap history length to prevent unbounded growth.

-  if (countryChanged) {
+  if (countryChanged) {
     partnerChangeHistoryLog.push({ /* ... */ });
+    // keep last 100
+    if (partnerChangeHistoryLog.length > 100) {
+      partnerChangeHistoryLog.splice(0, partnerChangeHistoryLog.length - 100);
+    }
   }

If you prefer stronger consistency, wrap a re-read/merge in a short transaction to avoid lost updates when multiple writers appear in the future. I can provide a transactional snippet if useful.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a754ea9 and c637fd1.

📒 Files selected for processing (3)
  • apps/web/lib/actions/partners/update-partner-profile.ts (2 hunks)
  • apps/web/lib/zod/schemas/partner-profile.ts (2 hunks)
  • packages/prisma/schema/partner.prisma (1 hunks)
🔇 Additional comments (4)
apps/web/lib/zod/schemas/partner-profile.ts (1)

7-7: LGTM — needed enum import.

apps/web/lib/actions/partners/update-partner-profile.ts (3)

8-10: LGTM — imports are appropriate.


268-270: Good early exit. Prevents unnecessary work when no compliance fields changed.


272-276: Guard is correct. Clear message and early block when payouts already enabled.

@steven-tey steven-tey merged commit dbb584a into main Oct 26, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the change-history-log branch October 26, 2025 23:48
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