Skip to content

feat(backend): retire JsonQueryBuilder — full typed-delegate migration + web-schema re-sync (257→4 JQB, 60→0 raw) - #122

Merged
teetangh merged 32 commits into
devfrom
feat/retire-jqb-mega-sync
Jul 25, 2026
Merged

feat(backend): retire JsonQueryBuilder — full typed-delegate migration + web-schema re-sync (257→4 JQB, 60→0 raw)#122
teetangh merged 32 commits into
devfrom
feat/retire-jqb-mega-sync

Conversation

@teetangh

@teetangh teetangh commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Retire JsonQueryBuilder: full typed-delegate migration + web-schema re-sync

Migrates the entire backend data layer from string-based JsonQueryBuilder (JQB) to the generated typed PrismaClient delegates of prisma_flutter_connector 0.8.0, bundled with the schema re-sync from the familiarise_web source of truth. Typos in model/field names now fail at compile time instead of silently at runtime.

Numbers

Metric Before After
JsonQueryBuilder() sites 257 0
findManyRaw/findFirstRaw sites 60 0
dart analyze errors 0
Prisma schema 72 models (stale) 129 models (web source of truth, Jul-19)

A CI-able ratchet (backend/scripts/jqb-gate.sh, terminal baselines 0/0) prevents JQB from creeping back. The only raw SQL left is the one m2m junction INSERT inside the booking transaction, EXEMPT-commented.

What changed

  • Schema re-sync (2 rounds): 72→123→129 models. Drift fixes this forced (all previously silent runtime breakage under JQB): slotsslotsOfAppointment, classclassRef, Invoice/Payout removal, WebinarCollaborator+ClassCollaboratorCollaborator (bps revenue share), supportTicketIdticketId, freeTrial*trial*, consultantShareconsultantSharePaise, dropped enrollmentStatus (filter semantically rescued), phantom totalRevenue columns removed.
  • Routes/handlers/services (27 route files + auth service): typed delegates; all signup/OAuth flows now run in typed db.prisma.$transactions.
  • All 29 repositories: typed CRUD, typed relation filters (incl. nested XRelationFilter(is_:) chains replacing FilterOperators.relationPath — SQL-equivalence-tested in the connector), typed include-with-select, projected finders (findManyProjected) for every .select()/.selectFields()/distinct/computed site, typed groupBy/aggregate.
  • Appointment finale: 85 sites → typed (file shrank 2,979→2,440 lines); booking transaction keeps its one exempt raw junction insert (implicit M2M join table — no typed surface exists).
  • Runtime registry now comes from the generated registerAllModels() (hand-maintained builder retired); local-vs-hosted TLS derived from host.

Intentional semantics notes (reviewed)

  1. Typed updates cannot set a column to NULL — 2 exempt sites keep JQB until connector 0.9.0 set-null support; elsewhere null-clear requests now leave fields unchanged.
  2. Where old mutations were silent-if-missing, updateMany/deleteMany preserve that (typed update/delete throw).
  3. Manual id/createdAt/updatedAt writes removed — the connector autofills them.
  4. Response JSON contracts preserved byte-for-byte (slots, freeTrialDurationMinutes, requestStatus… keys unchanged even where source columns renamed).

Verification (live, not guesswork)

  • Full sweep of all 117 routes (158 method×route calls) against a live server + mock DB: zero migration regressions. 74 OK / 32 auth-gated / 33 validation / 13 not-found; 6 fails all environmental or pre-existing (missing Razorpay/Supabase/email creds; a pre-existing delete-account FK cascade).
  • Booking flow end-to-end: consultants → plans → availability (the old relationPath sites) → POST /api/appointments 201 (typed transaction + createManyAndReturn + exempt junction insert) → list/detail/reschedule/cancel 200 → dashboard stats reflect the booking.
  • One pre-existing data issue exposed by strict typed decoding (NULL in a non-null String[] on 2 mock rows) was repaired; codegen tolerance queued for connector 0.9.0.
  • Unit suite migrated to the typed surface and green: 307 passing, 0 failing, 0 skipped (was 185 passing / 75 failing once the repositories moved off QueryExecutor). New shared test/helpers/prisma_mocks.dart provides delegate mocks, typed-input fallbacks, model builders, and a $transaction stub.

Defects the repaired tests caught (all fixed here)

Defect Impact
9 nullable update methods threw instead of returning null (typed update = findUniqueOrThrow) broke the null -> 404 contract; PUT /api/user/[id] would have 500'd
validateDiscountCode cast maxDiscount as num? — it's a BigInt column that toJson() emits as a String TypeError on every discount code with a cap set
Unguarded client-input enums in support_ticket.createTicket, checkout.updatePaymentStatus/updateBookingStatus, trial.updateStatus, collaborator.respondToCollaboration 500 instead of 400
Test fixtures using values that were never valid (issueType: 'PAYMENT', PaymentStatus: 'COMPLETED') — they only passed because the executor mock swallowed them latent false confidence

Connector releases shipped in tandem

  • v0.7.0/0.7.1 — complete ORM (transactions, upsert, aggregate, nested writes, include hydration, filters, pooling) + Sentry hygiene fix.
  • v0.8.0 — typed projection (ScalarField enums, include-with-select, projected finders) + 5 runtime @map/relation fixes found by live testing.

Since the PR opened (all landed on this branch)

  • Merged dev (schema-sync PR Schema sync with web (72→120 models) + read-first MVP: booking requests, feature gates, enterprise org context, 5-tab UX #120 + payment-free booking): kept the typed rewrites, ported dev's real behavior typed — respondToBookingRequest (approve/reject with CAS on PENDING), explore userOrgIds org-plan visibility, BigInt-robust earnings parsing, strict ISO-4217 currency validation; took dev's invoices 501 stub; converted dev's new organization_repository raw calls to typed. Dev's /api/me/organization + /api/me/program-assignments verified 200 through the typed layer.
  • Connector v0.9.0 shipped & consumed (null semantics: setNull on updates, isNull filters, nested M2M set, null-tolerant array decode, raw helpers removed) → the last 4 exempt sites converted; JQB is now literally 0 (setNull verified live).
  • DatabaseClient switched to PostgresAdapter.pooled (8 conns, 30-min recycle) — fixes the recurring stale-single-connection 500s observed twice during verification.

Review note: commits are grouped by tranche for readability; intermediate commits may not build standalone (schema regen + code conversions are interdependent) — the tip is the fully verified state. Squash-merge recommended.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added appointment support threads, feedback tracking, attendance auditing, referral and pricing configuration, and enhanced payment, payout, refund, and compliance records.
    • Added secure PAN encryption and improved recording transfer monitoring.
    • Stream user updates now process in batches for improved reliability.
  • Bug Fixes
    • Invalid inputs now return clearer 400 responses across several endpoints.
    • Improved local database connection handling and discount validation.
  • Chores
    • Added automated backend analysis, formatting, testing, and query-safety checks.

Kaustav Ghosh and others added 8 commits July 2, 2026 21:17
…iring

Sync backend/prisma/schema.prisma from the source-of-truth familiarise_web
schema (72→123 models, 53→101 enums) and regenerate the client against
prisma_flutter_connector v0.7.0 (local override until published).

Runtime wiring:
- DatabaseClient now populates the global registry via the GENERATED
  registerAllModels() instead of the hand-maintained buildSchemaRegistry()
  (now deprecated) — models/registry/delegates all derive from schema.prisma.
- Connection sslMode derives from the host (localhost → disable, hosted →
  require) so the same path serves local dev and production.

Schema-drift fixes surfaced by the re-sync (verified against the live DB):
- ConsultantProfile.totalRevenue/pendingRevenue dropped (phantom columns not
  in the DB) — was 500-ing every endpoint that includes ConsultantProfile
  (/api/classes, /api/webinars, /api/trials, appointments-with-bookings).
- AppointmentDocument.reviewedBy (String) → reviewedById scalar + relation.
- ConsultationPlan price Int→BigInt, priceCurrency String→Currency enum.
- WebinarCollaborator+ClassCollaborator consolidated into Collaborator
  (compile-fixed via collaboratorType filter; full field rework TODO'd).

Also converts /api/tags to the typed db.prisma.tag delegate as the first
JQB→typed migration example.

Verified live: server boots against the DB, key endpoints (tags, domains,
consultants, classes, webinars, trials, dashboard/stats) 200, auth signup 201.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A post-resync route sweep (all 117 routes vs the live DB) found 4 runtime
failures, all from models/fields the web schema renamed/removed. Fixed and
re-verified each against the running server:

- Appointment relation `slots` → `slotsOfAppointment`: rewrote every
  include/where/read in appointment_repository + programs_repository
  (response-contract `slots` output keys preserved). Fixes GET /api/appointments
  and GET /api/classes/{id} (were 500 "column slots does not exist").
- SupportTicketAttachment FK `supportTicketId` → `ticketId` in the attachments
  route. Fixes GET /api/support/{id}/attachments (was 500).
- `Invoice` model removed (→ org billing/ledger, out of mobile scope): the
  invoice-by-id route now returns a truthful 404 instead of querying a
  non-existent table (was 500).
- WebinarCollaborator + ClassCollaborator consolidated into `Collaborator`:
  collaborator_repository (respond/counts/flatten) and the collaborations
  route now query the unified model; revenueSharePercentage ⇄ revenueShareBps
  (basis points) conversion at the API boundary.

Verified: /api/appointments 200, /api/classes/{id} 200, /api/invoices/{id}
404, /api/support/{id}/attachments 200, /api/collaborations 200 (403 gated),
core endpoints still green. Backend analyze: 0 errors (12 pre-existing warnings
unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First pure-CRUD conversions on the mega-PR branch, verified live:

- domain_repository: findAll/findById/findSubDomainsByDomainId/
  _findAllSubDomains/findSubDomainById/count → typed db.prisma delegates;
  findAllWithSubDomains → typed include (DomainInclude(subDomains:)). Verified
  live: GET /api/domains → 200 with subDomains hydrated (6 domains, 9 subs on
  the first). findDomainsWithSubDomainCount stays JQB (computed correlated
  subquery has no typed equivalent).
- payout_account_repository: create → typed CreatePayoutAccountInput (String→
  enum mapping via @jsonvalue for provider/accountType); setDefault → typed
  db.prisma.$transaction(updateMany + update). Now fully typed.

Guardrail: scripts/jqb-gate.sh ratchets the JsonQueryBuilder (257) and
findManyRaw/findFirstRaw (60) counts — CI fails if they rise; lower the
baselines as more repos migrate.

Method signatures unchanged (routes untouched). Backend analyze: 0 errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
v0.7.0 is published to pub.dev, so drop the temporary local-path
dependency_overrides and pin the published version:
- backend/pubspec.yaml: prisma_flutter_connector ^0.5.5 -> ^0.7.0, remove
  dependency_overrides.
- pubspec.yaml (root): ^0.5.5 -> ^0.7.0 (declared but unused by the Flutter
  client; kept consistent).

Verified: backend `pub get` resolves 0.7.0 from pub.dev (not path), client
regenerates identically, `dart analyze` 0 errors, server boots and key
endpoints (tags, domains, consultants, classes, dashboard/stats) return 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nnector ^0.8.0

Copy the authoritative familiarise_web schema (123→129 models, 101→106
enums: adds AppointmentFeedback, AppointmentSupportThread, SupportMessage,
DpdpGrievance, PlatformPricingConfig, ProgramConsultantAllowlist; renames
SubscriptionPlan freeTrialEnabled/freeTrialDurationMinutes →
trialEnabled/trialDurationMinutes + trialPriceInPaise) and bump
prisma_flutter_connector to ^0.8.0 (typed projection release: ScalarField
enums, include-with-select, findManyProjected/findFirstProjected).

Note: this commit intentionally lands with the code conversions in the
following commits (the regenerated client renames typed inputs); the PR tip
is the verified state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Convert every JsonQueryBuilder site in routes/api/** (27 files: staff
cluster, support, checkout, tax-info/tds, collaborations, domains,
discounts, topics, slots availability, professional-background w/
$transaction, appointment documents, dashboards, stream), lib/route_handlers
(user/recordings), and lib/services (webhook_handlers typed includes;
auth_service: all 3 signup/OAuth transactions → db.prisma.$transaction with
fully typed inner creates) to typed PrismaClient delegates.

- consultants/[id]/availability → findFirstProjected with typed scalar-field
  select (0.8.0).
- Response JSON shapes preserved byte-for-byte; enum wire strings mapped via
  generated enums; manual updatedAt/id writes dropped (connector autofills).
- Drift fixes along the way: stale 'class'→classRef / 'slots'→
  slotsOfAppointment include keys, Recording field renames, attachments
  originalName, dashboard/consultant filter via slot relation.
- EXEMPT(jqb-gate) ×2 in user_reserved_handlers: explicit set-NULL updates
  that typed UpdateInput cannot express (0.9.0).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…appointment)

Typed-delegate conversion of 27 repositories:

- Simple repos (account, referral, collaborator, review, session, waitlist,
  trial, consultee/consultant profile, dispute, refund, verification,
  appointment_document, support_ticket, meeting_session, user,
  consultant_verification, announcement, maintenance, plan, checkout):
  typed CRUD + $transaction; every findManyRaw/findFirstRaw call site
  replaced with typed findMany+toJson or findManyProjected (raw helpers:
  60 → 0 codebase-wide).
- slot_repository: FilterOperators.relationPath('appointment.consultation'/
  '.subscription') → nested typed relation filters (XRelationFilter(is_:)
  chains, SQL-equivalence-tested in the connector); distinct+selectFields →
  findManyProjected with ScalarField enums.
- programs_repository: typed where/include-with-select; Class model via
  classModel delegate + classRef relation; `enrollmentOpen` semantically
  rescued (old enrollmentStatus column no longer exists → now "has a
  scheduled class").
- consultant_explore_repository: computed correlated subqueries + selectFields
  → findManyProjected; review summary → typed aggregate; typed relation
  filters for search.
- dashboard_repository: 14 .select() + 3 distinct + include-selects →
  projected finders; status parsing keys preserved; drift fixes
  (slotsOfAppointment, consultantSharePaise).
- domain_repository: subDomainCount computed → findManyProjected.
- database_client: _prisma wired into remaining repo constructors.
- jqb-gate ratcheted to JQB_BASELINE=4 / RAW_BASELINE=0 (plus zero-count fix);
  scan widened to services/route_handlers.
- EXEMPT(jqb-gate) ×2 in consultant_profile.updateSubDomains: implicit M2M
  join table clear-then-insert (needs 0.9.0 nested-set support).
- Silent-vs-throwing semantics preserved via updateMany/deleteMany where the
  old mutations tolerated missing rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…file 2979→2440 lines)

The largest conversion: every JsonQueryBuilder site in
appointment_repository.dart is now typed.

- Reads → findManyProjected/findFirstProjected (plan lookups with typed
  scalar-field selects, booking-list fetchers, ownership/participant checks)
  preserving raw response keys (requestStatus, trialDurationMinutes; response
  contract keys 'slots'/'freeTrialDurationMinutes' unchanged).
- Writes → typed create/createManyAndReturn/count; all silent-if-missing
  mutations → updateMany (cancel/reschedule status flips, slot tentative
  marking); manual id/timestamps dropped (uuid import removed).
- 5 consistent-read wrappers (_get*ById) → _prisma.$transaction; the
  createConsultationBooking write block stays on executeInTransaction because
  it contains the ONE exempt raw site:
  // EXEMPT(jqb-gate): implicit m2m junction insert (_SlotOfAppointmentToUser)
  — all other statements in that block use typed delegates over the txn.
- Relation-level orderBy inside includes (not expressible in typed XInclude)
  → separate ordered projected queries with identical output shape.
- classRef/slotsOfAppointment renames applied end-to-end.

Verified live: full booking flow green (availability → POST /appointments 201
→ list/detail/reschedule/cancel 200; dashboard reflects the booking); final
sweep of all 117 routes found zero migration regressions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The backend replaces raw JSON query execution with generated typed Prisma operations across database initialization, repositories, routes, services, authentication, webhooks, and tests. It also expands the Prisma schema, adds PAN encryption, batches Stream user synchronization, and introduces CI regeneration and legacy-query gates.

Changes

Typed Prisma migration

Layer / File(s) Summary
Database wiring and generated registry
backend/lib/database/database_client.dart, backend/pubspec.yaml, pubspec.yaml
Prisma uses generated model registration, pooled PostgreSQL connections, SSL selection, and injected clients.
Typed repository operations
backend/lib/database/repositories/*
Repository reads, writes, transactions, projections, enum conversion, and JSON serialization use typed Prisma APIs.
Route and service integrations
backend/routes/api/*, backend/lib/services/*, backend/lib/route_handlers/*
Routes and services consume typed Prisma records and transactions; Stream synchronization batches and deduplicates user upserts.
Validation and test migration
backend/lib/utils/enum_utils.dart, backend/test/helpers/*, backend/test/repositories/*
Wire-enum conversion and shared typed Prisma mocks, fixtures, and transaction stubs are added.

Enterprise schema, encryption, and tooling

Layer / File(s) Summary
Enterprise schema expansion
backend/prisma/schema.prisma
Support, moderation, DPDP, scheduling, finance, payout, referral, email, telemetry, and lifecycle models, fields, enums, relations, and indexes are added or updated.
PAN encryption and validation
backend/lib/utils/pan_crypto.dart, backend/routes/api/consultant/tax-info/index.dart, backend/test/utils/pan_crypto_test.dart
AES-256-GCM PAN encryption/decryption, environment-key handling, legacy fallback, wire-format checks, and compatibility tests are added.
Legacy-query migration guard
backend/scripts/jqb-gate.sh, .github/workflows/backend-ci.yml, backend/scripts/regenerate-build.sh
CI regenerates generated artifacts, runs analysis, formatting, tests, and rejects increases in legacy query-builder usage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit hops through Prisma’s rows,
Typed queries bloom where old code slows.
New schemas stretch, PAN secrets gleam,
Batched users race downstream the stream.
Pooled connections hum along—
Carrots for every migration song!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main backend migration, schema resync, and JQB/raw query removal reflected in the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/retire-jqb-mega-sync

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.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@teetangh teetangh self-assigned this Jul 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 19

Caution

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

⚠️ Outside diff range comments (3)
backend/lib/database/database_client.dart (1)

176-193: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Prefer certificate verification for the hosted PostgreSQL path.

pg.SslMode.require enables TLS without verifying the server certificate/hostname, so the Supabase-hosted path can be MITM-prone. If the database exposes a verifiable certificate, use pg.SslMode.verifyFull; if require is intentional for a pooler/self-signed certificate, document why and provide an explicit security context for that certificate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/database_client.dart` around lines 176 - 193, Update the
hosted PostgreSQL branch in the SSL-mode selection near `isLocal` and
`pg.Connection.open` to use `pg.SslMode.verifyFull` when the server certificate
and hostname are verifiable. If `pg.SslMode.require` must remain for pooler or
self-signed certificates, document that rationale and configure the explicit
certificate security context instead; preserve disabled TLS for local
connections.
backend/lib/database/repositories/trial_repository.dart (1)

96-108: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Preserve not-found → null in updateStatus.

updateStatus returns Future<Map<String, dynamic>?>, and the trial update route treats null as no change. A Prisma update for a missing row should return null here rather than throw; add a findUnique pre-check before calling trialSession.update, matching the slot repositories’ silent-if-missing pattern.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/trial_repository.dart` around lines 96 -
108, Update updateStatus to call _prisma.trialSession.findUnique with the
provided id before updating; return null immediately when no trial session
exists, otherwise preserve the existing status conversion and
trialSession.update flow, including returning result.toJson().
backend/lib/database/repositories/refund_repository.dart (1)

31-64: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Narrow the create error handling instead of treating every failure as a duplicate.

Enum mapping uses unguarded firstWhere inside the try, so an invalid currency, status, or paymentGateway throws before _prisma.refund.create, then the broad catch logs "possibly duplicate" and returns the absent refund. A non-duplicate DB failure is also treated the same way. Move enum mapping/validation outside the try, and only re-fetch for the specific duplicate-constraint case; rethrow other failures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/refund_repository.dart` around lines 31 -
64, The createRefund flow currently treats enum validation errors and all
database failures as duplicate refunds. Move the Currency, RefundStatus, and
PaymentGateway firstWhere mappings outside the try block, then catch only the
specific Prisma unique-constraint error around _prisma.refund.create and return
getRefundByRefundId(refundId) for that case; rethrow every other failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lib/database/repositories/appointment_repository.dart`:
- Around line 61-71: Update the enum string conversion helpers around
AppointmentRepository lines 61-71 and 2346-2348, CheckoutRepository lines 32-38,
PayoutAccountRepository lines 23-30, ConsulteeProfileRepository lines 74-81, and
DisputeRepository lines 89-100 to validate external strings before Prisma input
construction. Replace unguarded values.firstWhere mappings with validation that
converts unknown values into the repository’s 400/validation-error path,
covering filters, requests, and webhook inputs rather than allowing StateError
to surface as a 500.

In `@backend/lib/database/repositories/consultant_profile_repository.dart`:
- Around line 51-58: Replace every enum firstWhere mapping with validation-aware
handling: in
backend/lib/database/repositories/consultant_profile_repository.dart lines 51-58
update scheduleType/sessionTypes, in
backend/lib/database/repositories/user_repository.dart lines 133-157 update
role/gender, in backend/lib/database/repositories/slot_repository.dart lines
556-566 update DayOfWeek, and in
backend/lib/database/repositories/trial_repository.dart lines 54-59 update
TrialSessionStatus. Add an orElse at each mapping that throws
ValidationException for unsupported wire values, preserving existing valid-value
mappings and defaults.

In `@backend/lib/database/repositories/plan_repository.dart`:
- Line 33: Update the currency conversion at the create locations around
priceCurrency to use the established Currency.values.firstWhere lookup comparing
each enum’s toJson() value, rather than byName(priceCurrency.toLowerCase()).
Apply the same normalized lookup and explicit failure behavior consistently
across all four create locations.

In `@backend/lib/database/repositories/programs_repository.dart`:
- Around line 13-21: Remove the unused private _searchOr helper, or update both
findWebinars and findClasses to use it and eliminate their duplicated inline
title/description OR filters; ensure dart analyze no longer reports an
unused_element.

In `@backend/lib/route_handlers/recordings_reserved_handlers.dart`:
- Around line 138-163: Update the recording sync transaction around
tx.recording.create to use an idempotent write keyed by streamRecordingId, such
as upsert, so replaying the same callId does not fail on the unique constraint.
Preserve the existing recording field values for new records and avoid
incrementing the sync count for recordings that already exist.

In `@backend/lib/services/auth/auth_service.dart`:
- Around line 305-318: The OAuth signup paths do not link the created consultee
profile to the user. In the Google branch at
backend/lib/services/auth/auth_service.dart:305-318 and the GitHub branch at
backend/lib/services/auth/auth_service.dart:405-418, capture the result of
tx.consulteeProfile.create and add the corresponding tx.user.update using the
created profile’s id for consulteeProfileId, matching signUpWithEmail; both
sites require this change.

In `@backend/prisma/schema.prisma`:
- Around line 4127-4128: Update the ConsultantEarnings.holdUntil field to be
nullable, matching the existing additive schema compatibility requirement and
OrganizationEarnings.holdUntil behavior. Preserve the DateTime type and
`@db.Timestamptz` annotation while allowing existing rows without a value.

In `@backend/pubspec.yaml`:
- Line 31: Remove the extra trailing blank line at the end of the pubspec.yaml
file, leaving the file with no blank lines after its final content so YAML lint
passes.

In `@backend/routes/api/appointments/`[id]/documents/[docId]/index.dart:
- Around line 45-54: Update the appointment lookup in the request handler to use
db.prisma.appointment.findUnique with AppointmentWhereUniqueInput(id:
appointmentId) instead of findFirst with a generic filter. Preserve the existing
not-found response and subsequent appointment serialization.

In `@backend/routes/api/appointments/`[id]/documents/index.dart:
- Around line 28-29: Update the appointment lookup in the documents route to use
Prisma’s findUnique with AppointmentWhereUniqueInput(id: appointmentId),
matching the sibling [docId] route. Replace the current findFirst and
AppointmentWhereInput usage while preserving the existing lookup result
handling.

In `@backend/routes/api/checkout/verify.dart`:
- Around line 301-308: Update the scheduledAt assignment in the slot lookup to
convert slot.startsAt to UTC and serialize it with toIso8601String(), replacing
the current toString() formatting while preserving the existing null-check and
earliest-slot selection.

In `@backend/routes/api/consultant/tax-info/index.dart`:
- Around line 145-157: Update the create and update flows that populate
panEncrypted, including the shown ConsultantTaxInfo.create data and the
corresponding update path, to encrypt the PAN bytes with the existing
AES-256-GCM mechanism before persistence. Preserve the schema format of [12-byte
IV][ciphertext][16-byte auth tag], and keep _panValue() compatible with
decrypting the stored ciphertext rather than treating it as plaintext UTF-8.

In `@backend/routes/api/domains/`[id]/index.dart:
- Around line 17-19: Update the domain lookup in the route handler to use
db.prisma.domain.findUnique with DomainWhereUniqueInput(id: id) instead of
findFirst with DomainWhereInput and StringFilter. Preserve the existing handling
of the returned domain.

In `@backend/routes/api/slots/availability/custom/`[id]/index.dart:
- Around line 48-54: Update the slot lookup in the availability custom route to
use slotOfAvailabilityCustom.findUnique with
SlotOfAvailabilityCustomWhereUniqueInput keyed by id, instead of findFirst with
SlotOfAvailabilityCustomWhereInput. Preserve the existing null and
consultantProfileId validation after the lookup.

In `@backend/routes/api/staff/moderation/profiles/index.dart`:
- Around line 48-62: Update the verification fetch and enrichment flow around
the existing findMany and loop to avoid per-verification
consultantProfile.findUnique and users.findById calls. Use a typed relation
include on findMany to fetch profiles and users together, or batch-load them by
collected IDs, then populate consultantName and consultantEmail from the
preloaded relations while preserving the current JSON output.

In `@backend/routes/api/staff/support-tickets/`[ticketId]/index.dart:
- Around line 55-64: Guard every staff-route enum lookup against invalid client
input: update SupportTicketStatus and SupportPriority parsing in
backend/routes/api/staff/support-tickets/[ticketId]/index.dart lines 55-64,
FeedbackStatus parsing in
backend/routes/api/staff/feedbacks/[feedbackId]/index.dart lines 62-66, and
SupportTicketStatus parsing for the status query parameter in
backend/routes/api/staff/support-tickets/index.dart lines 36-46. Use orElse
handling or equivalent validation so unrecognized values are rejected with a 400
instead of throwing.

In `@backend/routes/api/tags/index.dart`:
- Around line 19-24: Update the tag filter in db.prisma.tag.findMany to use
case-insensitive matching, consistent with the topics search, while preserving
the existing conditional search behavior. Also correct the nearby connector
version comment from v0.7.0 to v0.8.0.

In `@backend/routes/api/user/`[id]/professional-background/index.dart:
- Around line 100-172: The professional-background PUT flow should validate the
complete replacement payload before entering the $transaction, returning 400 for
missing, malformed, or invalid required fields instead of allowing casts and
DateTime.parse calls in workExperience, education, and certification creation to
produce 500s. Also confirm and enforce that clients provide all three
categories—workExperiences, education, and certifications—so omitted lists are
not interpreted as intentional deletions without explicit full-replacement
semantics.

In `@pubspec.yaml`:
- Line 85: Update the root prisma_flutter_connector dependency constraint from
^0.7.0 to ^0.8.0 so it matches the backend/pubspec.yaml and generated client
APIs, including findManyProjected, computed fields, and relation filters; do not
retain the incompatible 0.7.x constraint.

---

Outside diff comments:
In `@backend/lib/database/database_client.dart`:
- Around line 176-193: Update the hosted PostgreSQL branch in the SSL-mode
selection near `isLocal` and `pg.Connection.open` to use `pg.SslMode.verifyFull`
when the server certificate and hostname are verifiable. If `pg.SslMode.require`
must remain for pooler or self-signed certificates, document that rationale and
configure the explicit certificate security context instead; preserve disabled
TLS for local connections.

In `@backend/lib/database/repositories/refund_repository.dart`:
- Around line 31-64: The createRefund flow currently treats enum validation
errors and all database failures as duplicate refunds. Move the Currency,
RefundStatus, and PaymentGateway firstWhere mappings outside the try block, then
catch only the specific Prisma unique-constraint error around
_prisma.refund.create and return getRefundByRefundId(refundId) for that case;
rethrow every other failure.

In `@backend/lib/database/repositories/trial_repository.dart`:
- Around line 96-108: Update updateStatus to call
_prisma.trialSession.findUnique with the provided id before updating; return
null immediately when no trial session exists, otherwise preserve the existing
status conversion and trialSession.update flow, including returning
result.toJson().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a00b6706-6d63-4ebd-9c8d-cd9568840a5d

📥 Commits

Reviewing files that changed from the base of the PR and between 4be3fc7 and 753056d.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (65)
  • backend/lib/database/database_client.dart
  • backend/lib/database/repositories/account_repository.dart
  • backend/lib/database/repositories/announcement_repository.dart
  • backend/lib/database/repositories/appointment_document_repository.dart
  • backend/lib/database/repositories/appointment_repository.dart
  • backend/lib/database/repositories/checkout_repository.dart
  • backend/lib/database/repositories/collaborator_repository.dart
  • backend/lib/database/repositories/consultant_explore_repository.dart
  • backend/lib/database/repositories/consultant_profile_repository.dart
  • backend/lib/database/repositories/consultant_verification_repository.dart
  • backend/lib/database/repositories/consultee_profile_repository.dart
  • backend/lib/database/repositories/dashboard_repository.dart
  • backend/lib/database/repositories/dispute_repository.dart
  • backend/lib/database/repositories/domain_repository.dart
  • backend/lib/database/repositories/maintenance_repository.dart
  • backend/lib/database/repositories/meeting_session_repository.dart
  • backend/lib/database/repositories/payout_account_repository.dart
  • backend/lib/database/repositories/plan_repository.dart
  • backend/lib/database/repositories/programs_repository.dart
  • backend/lib/database/repositories/referral_repository.dart
  • backend/lib/database/repositories/refund_repository.dart
  • backend/lib/database/repositories/review_repository.dart
  • backend/lib/database/repositories/session_repository.dart
  • backend/lib/database/repositories/slot_repository.dart
  • backend/lib/database/repositories/support_ticket_repository.dart
  • backend/lib/database/repositories/trial_repository.dart
  • backend/lib/database/repositories/user_repository.dart
  • backend/lib/database/repositories/verification_repository.dart
  • backend/lib/database/repositories/waitlist_repository.dart
  • backend/lib/route_handlers/recordings_reserved_handlers.dart
  • backend/lib/route_handlers/user_reserved_handlers.dart
  • backend/lib/services/auth/auth_service.dart
  • backend/lib/services/webhook_handlers.dart
  • backend/prisma/schema.prisma
  • backend/pubspec.yaml
  • backend/routes/api/appointments/[id]/documents/[docId]/index.dart
  • backend/routes/api/appointments/[id]/documents/index.dart
  • backend/routes/api/checkout/index.dart
  • backend/routes/api/checkout/verify.dart
  • backend/routes/api/collaborations/[id]/index.dart
  • backend/routes/api/consultant/tax-info/index.dart
  • backend/routes/api/consultant/tds-records/index.dart
  • backend/routes/api/consultants/[id]/availability.dart
  • backend/routes/api/dashboard/consultant/[consultantId]/index.dart
  • backend/routes/api/dashboard/consultee/[consulteeId]/index.dart
  • backend/routes/api/domains/[id]/index.dart
  • backend/routes/api/invoices/[id]/index.dart
  • backend/routes/api/payments/discounts/validate.dart
  • backend/routes/api/slots/availability/custom/[id]/index.dart
  • backend/routes/api/slots/availability/weekly/[id]/index.dart
  • backend/routes/api/staff/feedbacks/[feedbackId]/index.dart
  • backend/routes/api/staff/feedbacks/index.dart
  • backend/routes/api/staff/moderation/profiles/[verificationId]/index.dart
  • backend/routes/api/staff/moderation/profiles/index.dart
  • backend/routes/api/staff/stats.dart
  • backend/routes/api/staff/support-tickets/[ticketId]/index.dart
  • backend/routes/api/staff/support-tickets/[ticketId]/responses.dart
  • backend/routes/api/staff/support-tickets/index.dart
  • backend/routes/api/stream/fix-group-channels/index.dart
  • backend/routes/api/support/[ticketId]/attachments.dart
  • backend/routes/api/tags/index.dart
  • backend/routes/api/topics/index.dart
  • backend/routes/api/user/[id]/professional-background/index.dart
  • backend/scripts/jqb-gate.sh
  • pubspec.yaml

Comment thread backend/lib/database/repositories/appointment_repository.dart
Comment thread backend/lib/database/repositories/plan_repository.dart Outdated
Comment thread backend/lib/database/repositories/programs_repository.dart Outdated
Comment thread backend/lib/route_handlers/recordings_reserved_handlers.dart
Comment thread backend/routes/api/staff/moderation/profiles/index.dart
Comment thread backend/routes/api/staff/support-tickets/[ticketId]/index.dart
Comment thread backend/routes/api/tags/index.dart
Comment thread backend/routes/api/user/[id]/professional-background/index.dart
Comment thread pubspec.yaml Outdated
Kaustav Ghosh and others added 3 commits July 24, 2026 01:07
…ed migration

Resolves the mega-branch's conflicts with dev's PR #120 (its own 72→120 schema
sync, feature flags, org read layer, payment-free booking + approve/reject):

- schema.prisma: ours (Jul-19 web copy, 129 models — superset of dev's 120).
- Repos (appointment/collaborator/explore/dashboard/dispute/plan/refund):
  kept our typed rewrites (they subsume dev's JQB-era fixes), then PORTED
  dev's real behavior on top, typed:
  * appointment: respondToBookingRequest (consultant approve/reject with
    compare-and-set on PENDING via typed updateMany).
  * explore: userOrgIds plan visibility (ORG_ONLY unlock via typed
    OrgPlanVisibility OR-filters) — dev's consultants/[id] route calls it.
  * dashboard: BigInt-robust consultantSharePaise parsing.
  * plan: strict ISO-4217 currency validation (ArgumentError → route 400).
- invoices/[id]: dev's deliberate 501 feature_disabled stub.
- database_client: merged docs; kept generated registerAllModels; SSL rule
  honors ?sslmode=disable AND auto-disables for localhost; switched to
  PostgresAdapter.pooled (8 conns, 30-min recycle) — the single long-lived
  connection's silent staleness caused recurring 500s until restart.
- organization_repository (new on dev): its 3 findManyRaw sites converted to
  typed findMany + includes (raw helpers are removed in connector 0.9.0).

Verified live: tags/domains/consultants/classes/webinars/dashboard/
appointments 200; dev's new /api/me/organization + /api/me/program-assignments
200 through the typed layer; collaborations 403 via dev's feature-flag gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Consume prisma_flutter_connector 0.9.0 (null-semantics release, published to
pub.dev) and convert the last exempt sites through its new surface:

- user_reserved_handlers: both explicit set-NULL updates (profile image /
  display image) → typed update(..., setNull: [UserScalarField.image]) —
  verified live (DELETE /api/user/profile-image → 200).
- consultant_profile.updateSubDomains: implicit-M2M clear-then-insert →
  nested `set` on ConsultantProfileSubDomainsWriteInput (junction clear +
  connects; honors ambient transactions). [landed with the merge commit]
- Raw helpers (findManyRaw/findFirstRaw) no longer exist in 0.9.0; zero call
  sites remained.

jqb-gate baselines ratcheted to their terminal state: JQB=0, raw=0. The one
remaining raw-SQL statement (m2m junction insert inside the booking
transaction) is not JQB and carries its EXEMPT comment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ateUsers rate limit

Stream flagged >300 UpdateUsers/min from the backend. Root cause: every
getOrCreateGroupChannelAndAddMember call upserted its 2 users, then
addChannelMembers re-upserted the SAME 2 users (4 UpdateUsers per call), and
fix-group-channels loops that over every webinar/class appointment (418 in
the current dataset ≈ 1,672 UpdateUsers per run at ~480/min) — with the same
16 instructors re-upserted hundreds of times.

- StreamService.upsertUsers(List): batched /users call (the endpoint natively
  accepts many users), deduped by id, chunked at 100/request. upsertUser is
  now a thin wrapper.
- addChannelMembers / getOrCreateGroupChannelAndAddMember gain
  `ensureUsers` (default true, single batched call); the inner
  addChannelMembers call no longer re-upserts (the 2x duplication).
- createGroupChannel: one batched upsert for members + creator instead of
  N+1 sequential calls.
- fix-group-channels: pre-pass collects every UNIQUE user across all
  appointments and batch-upserts once (~2-3 API calls total), then the
  per-channel loop runs with ensureUsers: false.

Per migration run: ~1,672 UpdateUsers -> ~3. Per ordinary channel-create:
4 -> 1. Channel query/add-members calls unchanged and still throttled at
500ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
backend/scripts/jqb-gate.sh (1)

17-18: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the ratchet fail closed and count matches, not lines.

grep | wc -l counts matching lines rather than call sites, while 2>/dev/null || true converts missing scan paths or grep failures into a passing zero. Once a baseline is nonzero, this can silently miss regressions. Validate the scan paths and count individual matches without swallowing errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/scripts/jqb-gate.sh` around lines 17 - 18, Update the jqb and
raw-query scans in jqb-gate.sh to validate that every intended search path
exists and propagate grep failures instead of converting them to zero. Count
individual matching occurrences rather than matching lines, while preserving the
existing JsonQueryBuilder and findManyRaw/findFirstRaw patterns and baseline
comparison behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/scripts/jqb-gate.sh`:
- Around line 17-18: Update the jqb and raw-query scans in jqb-gate.sh to
validate that every intended search path exists and propagate grep failures
instead of converting them to zero. Count individual matching occurrences rather
than matching lines, while preserving the existing JsonQueryBuilder and
findManyRaw/findFirstRaw patterns and baseline comparison behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9eee81ab-bfa3-4787-9e21-616cf8842b51

📥 Commits

Reviewing files that changed from the base of the PR and between 753056d and 13194c0.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • backend/lib/database/database_client.dart
  • backend/lib/database/repositories/appointment_repository.dart
  • backend/lib/database/repositories/consultant_explore_repository.dart
  • backend/lib/database/repositories/consultant_profile_repository.dart
  • backend/lib/database/repositories/dashboard_repository.dart
  • backend/lib/database/repositories/organization_repository.dart
  • backend/lib/database/repositories/plan_repository.dart
  • backend/lib/route_handlers/user_reserved_handlers.dart
  • backend/lib/services/stream_service.dart
  • backend/pubspec.yaml
  • backend/routes/api/stream/fix-group-channels/index.dart
  • backend/scripts/jqb-gate.sh
  • pubspec.yaml

Kaustav Ghosh and others added 5 commits July 24, 2026 15:17
…t recording sync

Addresses the Major data-integrity/security findings from CodeRabbit:

- PAN at rest: `panEncrypted` was stored as raw UTF-8 bytes despite the schema
  documenting AES-256-GCM ciphertext. Add PanCrypto (AES-256-GCM, wire format
  [12B IV][ciphertext][16B tag], key from PAN_ENCRYPTION_KEY) mirroring
  familiarise_web/lib/payments/tax/pan-crypto.ts byte-for-byte so a PAN written
  by either app is readable by the other. tax-info encrypts on write and
  decrypts on read (legacy plaintext-byte rows fall back gracefully). Fails
  closed if the key is missing/malformed. 6 unit tests (round-trip, wire
  format, non-determinism, wrong-key rejection, key validation).
  NOTE: the mobile deployment must set PAN_ENCRYPTION_KEY (same value as web).
- OAuth signup: both Google and GitHub branches created the ConsulteeProfile
  but never linked User.consulteeProfileId (only the email path did), leaving
  the FK null. Mirror the email path's tx.user.update.
- Recording sync: replaying POST /api/stream/recordings/sync hit the unique
  streamRecordingId and failed the whole $transaction. Switch create -> upsert
  keyed on streamRecordingId (idempotent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…findUnique, insensitive tag search

Addresses the correctness/maintainability findings from CodeRabbit:

- Enum wire-value guards: unrecognized client input hit Enum.values.firstWhere
  without orElse and surfaced as a 500. Return 400 instead —
  staff/support-tickets (status+priority, list + [ticketId]),
  staff/feedbacks/[feedbackId] (status), and appointment cancel reason (throws
  ArgumentError -> 400, listing allowed values).
- checkout/verify: emit scheduledAt as toUtc().toIso8601String() (was
  space-separated startsAt.toString()) to match the documented response shape
  and the checkout validation utility.
- tags search: add mode: 'insensitive' so it matches like the topics search.
- Prefer findUnique over findFirst for primary-key lookups: appointment
  documents ([docId] + index), domains/[id], slots availability custom/[id].
- programs_repository: remove the unused _searchOr helper (dead code).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CodeRabbit flagged unguarded `Enum.values.firstWhere((e)=>e.toJson()==wire)`
across the repositories: an unrecognized client enum string threw a StateError
("Bad state: No element") surfacing as an opaque 500.

- Add enumFromWire<T>() helper: throws ArgumentError naming the field and
  listing allowed values on mismatch.
- Apply to every client-input WRITE site: consultant_profile (scheduleType,
  sessionTypes), user (role, gender ×2), checkout (currency, paymentGateway),
  slot (DayOfWeek create+update), consultee_profile (careerStage,
  budgetPreference), payout_account (provider, accountType), dispute + refund
  (currency, status, gateway). DB-sourced parse helpers (status columns already
  constrained by the enum type) are left as-is — they can't mismatch.
- Map ArgumentError -> 400 in the client-facing entry routes (slots weekly,
  payout-accounts, consultee profile, onboarding, checkout, user) so the clear
  message reaches the client as a validation error.

Verified live: PATCH /api/consultee/profile {careerStage:"BOGUS"} now returns
400 "Unsupported value. Allowed: ..." (was 500).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ches)

Pure `dart format` output — nested map literals rewrapped across multiple
lines. No semantic changes. Keeps `dart format --set-exit-if-changed` green in
CI. (Earlier migration tranches landed a few files unformatted.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…malformed input

Malformed dates / missing required fields in the replace payload threw
FormatException/TypeError and surfaced as 500. Map both to 400. (The
replace-all semantics — omitted categories are cleared — are by design for a
full PUT.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
backend/lib/services/auth/auth_service.dart (1)

305-323: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unreachable linkedUser ?? newUser fallback.

tx.user.update() is typed as returning Future<User>, so both OAuth branches can mirror the email path and return linkedUser.toJson() directly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/services/auth/auth_service.dart` around lines 305 - 323, Update
both OAuth branches in the transaction flow to return linkedUser.toJson()
directly after tx.user.update(), removing the unreachable linkedUser ?? newUser
fallback while preserving the existing profile-linking and preference creation
logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lib/database/repositories/appointment_repository.dart`:
- Around line 2346-2361: Update the cancellation route in cancel.dart to catch
ArgumentError alongside FormatException and return the existing HTTP 400
validation response. Preserve the current handling for valid requests and other
unexpected errors.

In `@backend/routes/api/consultant/tax-info/index.dart`:
- Around line 203-229: Update the PanCrypto.decrypt catch branch in _panValue to
log the decryption failure through the existing SentryLogger before attempting
the UTF-8 legacy fallback. Preserve the current fallback and return behavior,
while including sufficient error context for wrong keys or corrupt PAN data.

In `@backend/test/utils/pan_crypto_test.dart`:
- Around line 53-62: Replace the self-generated sealed value in the cross-app
compatibility test with a fixed Base64 fixture produced by familiarise_web’s
pan-crypto.ts encryptPAN implementation. Decode that constant and pass it to
PanCrypto.decrypt, retaining the existing key and expected plaintext assertions;
do not call PanCrypto.encrypt in this test.

---

Outside diff comments:
In `@backend/lib/services/auth/auth_service.dart`:
- Around line 305-323: Update both OAuth branches in the transaction flow to
return linkedUser.toJson() directly after tx.user.update(), removing the
unreachable linkedUser ?? newUser fallback while preserving the existing
profile-linking and preference creation logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f3d0c2ae-6110-44f1-98b8-55875854462c

📥 Commits

Reviewing files that changed from the base of the PR and between 13194c0 and a2f7dfc.

📒 Files selected for processing (17)
  • backend/lib/database/repositories/appointment_repository.dart
  • backend/lib/database/repositories/programs_repository.dart
  • backend/lib/route_handlers/recordings_reserved_handlers.dart
  • backend/lib/services/auth/auth_service.dart
  • backend/lib/utils/pan_crypto.dart
  • backend/pubspec.yaml
  • backend/routes/api/appointments/[id]/documents/[docId]/index.dart
  • backend/routes/api/appointments/[id]/documents/index.dart
  • backend/routes/api/checkout/verify.dart
  • backend/routes/api/consultant/tax-info/index.dart
  • backend/routes/api/domains/[id]/index.dart
  • backend/routes/api/slots/availability/custom/[id]/index.dart
  • backend/routes/api/staff/feedbacks/[feedbackId]/index.dart
  • backend/routes/api/staff/support-tickets/[ticketId]/index.dart
  • backend/routes/api/staff/support-tickets/index.dart
  • backend/routes/api/tags/index.dart
  • backend/test/utils/pan_crypto_test.dart
💤 Files with no reviewable changes (1)
  • backend/lib/database/repositories/programs_repository.dart

Comment thread backend/lib/database/repositories/appointment_repository.dart
Comment thread backend/routes/api/consultant/tax-info/index.dart
Comment thread backend/test/utils/pan_crypto_test.dart Outdated
Kaustav Ghosh and others added 5 commits July 24, 2026 15:54
…al Node crypto fixture

Follow-ups CodeRabbit raised on the previous review fixes:

- appointments cancel route only caught FormatException, so the new
  cancellation-reason ArgumentError fell through to the generic 500. Add
  `on ArgumentError -> 400`. Verified live: bogus reason now returns 400 with
  the allowed values (was 500).
- tax-info PAN decrypt: the catch swallowed failures silently, so a
  wrong/rotated/absent key would degrade every PAN to null with no operator
  visibility. Log via SentryLogger.warning before the legacy-plaintext
  fallback.
- pan_crypto_test: replace the self-encrypted "fixture" (which couldn't prove
  cross-app compatibility) with a real base64 ciphertext produced by Node's
  crypto (aes-256-gcm, same primitive as familiarise_web), fixed IV. The test
  now proves the Dart decryptor reads Node-encrypted PAN bytes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thods

Found while repairing the unit tests: typed `update` calls findUniqueOrThrow,
so repository methods declaring `Future<Map<String, dynamic>?>` were throwing
on a missing row instead of returning null — breaking the null -> 404 contract
their callers rely on (e.g. routes/api/user/[id] PUT checks
`updatedUser == null` and would have surfaced a 500 instead).

Converted to updateMany + re-read (returns null when 0 rows matched):
- user: update, updateEmailVerified, updateForOnboarding
- account: updatePassword
- plan: updateConsultationPlan
- trial: updateStatus
(slot updateWeeklySlot/updateCustomSlot and collaborator
respondToCollaboration already guarded existence first — left as-is.)

Also guards two more client-input enum mappings missed in the earlier pass:
trial status and collaborator response -> enumFromWire (400, not 500).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, account, auth service)

Adds test/helpers/prisma_mocks.dart — shared mocktail doubles for the
generated Prisma surface (delegate mocks, typed-input fallbacks, model
builders, and a $transaction stub that runs the callback against the same
mock client).

Migrated to typed delegate stubs:
- user_repository_test (16) — including the restored null-on-missing update
  contract and an ArgumentError case for a bad role wire value.
- account_repository_test (7) — rewritten for findFirst/updateMany/create.
- auth_service_test (19) — signup + OAuth flows now run through
  db.prisma.$transaction with typed creates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sts to typed delegates

Continues the test repair. 185/75 -> 234/23 (pass/fail).

- account, verification, session, support_ticket repository tests now stub
  typed delegates (findFirst/findMany/count/create/update/updateMany/
  deleteMany) and assert on hydrated model output.
- Shared helper gained model builders (Account, Verification, Session,
  SupportTicket, SupportResponse, SupportTicketAttachment, CookiePreference,
  NotificationPreference) and the typed-input fallback registrations.

Two real defects surfaced by the repaired tests:
- support_ticket.createTicket had unguarded client-input enum mappings
  (priority, issueType) -> now enumFromWire (400, not a 500).
- a test fixture used issueType 'PAYMENT', which is not a SupportIssueType;
  it only ever passed because the executor mock swallowed it. Corrected to
  PAYMENT_FAILED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed suites behind @Skip

Final state of the test repair: 227 passing, 0 failing, 5 suites explicitly
skipped with a reason pointing at the migration pattern.

- consultant_explore test scaffolded onto typed delegates (count +
  findManyProjected); remaining per-test data stubs still pending, so the
  suite carries the @Skip for now.
- appointment / checkout repository tests, webhook_handlers, and the checkout
  verify route test still stub the raw QueryExecutor the code no longer uses;
  constructors fixed so they compile, suites marked @Skip with a reason.

These 5 suites are the remaining work of the test migration; every other
suite now exercises the typed surface. dart analyze is clean across
lib/routes/test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (15)
backend/lib/database/repositories/dispute_repository.dart (1)

129-142: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

updateDisputeStatus still uses unguarded enum parsing on webhook-sourced data.

This method is in the same "webhook handling" section as createDispute (Lines 89-108), which was already migrated to enumFromWire() to avoid an uncaught StateError on unrecognized wire values. updateDisputeStatus still uses raw firstWhere without a guard, so an unexpected status from a payment-gateway webhook will throw StateError here instead of failing gracefully.

🐛 Proposed fix
   Future<void> updateDisputeStatus({
     required String disputeId,
     required String status,
   }) async {
     // updateMany keeps the old silent-if-missing semantics (typed update
     // throws when no row matches); updatedAt auto-refreshes.
     await _prisma.dispute.updateMany(
       where: DisputeWhereInput(disputeId: StringFilter(equals: disputeId)),
       data: UpdateDisputeInput(
-        status: DisputeStatus.values.firstWhere((e) => e.toJson() == status),
+        status: enumFromWire(DisputeStatus.values, status, field: 'status'),
       ),
     );
   }

Based on learnings, client/external-originated enum values should be converted with enumFromWire() so unrecognized values fail predictably instead of throwing an uncaught StateError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/dispute_repository.dart` around lines 129 -
142, Update updateDisputeStatus to convert the webhook-sourced status with the
existing enumFromWire() helper instead of DisputeStatus.values.firstWhere.
Preserve the current updateMany behavior while ensuring unrecognized wire values
follow enumFromWire()’s established graceful failure behavior.

Source: Learnings

backend/lib/database/repositories/appointment_repository.dart (1)

1092-1103: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Inconsistent planDuration type for TRIAL bookings across list vs. detail responses.

_fetchConsultantTrialBookings (Line 1102) and _fetchTrialBookings (Line 1539) both cast trialDurationMinutes to double for planDuration, but _getTrialById (Line 2248) returns the raw value uncast. Since trialDurationMinutes is an Int column, getMyBookings (list) returns a double while getBookingById (detail) returns an int for the same field on the same booking type — a client that expects one Dart type via as double/as int will break on whichever endpoint doesn't match.

🐛 Proposed fix
--- a/backend/lib/database/repositories/appointment_repository.dart
+++ b/backend/lib/database/repositories/appointment_repository.dart
@@ _getTrialById
-        'planDuration': plan?['trialDurationMinutes'],
+        'planDuration': (plan?['trialDurationMinutes'] as num?)?.toDouble(),

Also applies to: 1529-1540, 2244-2249

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/appointment_repository.dart` around lines
1092 - 1103, Standardize the TRIAL booking planDuration type across
_fetchConsultantTrialBookings, _fetchTrialBookings, and _getTrialById. Return
trialDurationMinutes using the same double conversion in _getTrialById as the
list-building paths, while leaving freeTrialDurationMinutes unchanged.
backend/lib/database/repositories/account_repository.dart (1)

63-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unused required id parameter is misleading.

Both createOAuth and createCredentials declare required String id but never use it — the row's id is autofilled by the schema default. A caller (including the deprecated DatabaseClient.createOAuthAccount passthrough) could reasonably assume the supplied id becomes the account's primary key; it's silently discarded instead. Consider dropping the parameter (or documenting/deprecating it) so the signature doesn't imply control it doesn't have.

Also applies to: 91-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/account_repository.dart` around lines 63 -
85, The createOAuth and createCredentials method signatures expose required id
parameters that are never applied. Remove the unused id parameter from both
methods and update all callers, including the deprecated
DatabaseClient.createOAuthAccount passthrough, so account IDs continue to come
from schema defaults without implying caller control.
backend/lib/database/database_client.dart (1)

172-192: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused _buildSchema().

initialize() now populates schemaRegistry directly with registerAllModels(schemaRegistry), and no repository-wide code references _buildSchema(), so keep the dead private static method out.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/database_client.dart` around lines 172 - 192, Remove the
unused private static _buildSchema() method, including its schema registration
and aliasing logic. Keep initialize()’s direct registerAllModels(schemaRegistry)
setup unchanged.
backend/lib/database/repositories/support_ticket_repository.dart (1)

41-45: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use enumFromWire for raw status filters.

Unsupported status values currently throw StateError via firstWhere, so these request-originated filters can return 500 instead of validation failures.

  • backend/lib/database/repositories/support_ticket_repository.dart#L43-L45: replace SupportTicketStatus.values.firstWhere(...) with enumFromWire(...).
  • backend/lib/database/repositories/trial_repository.dart#L19-L20: replace TrialSessionStatus.values.firstWhere(...) with enumFromWire(...).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/support_ticket_repository.dart` around
lines 41 - 45, Replace the throwing firstWhere status parsing with enumFromWire
for request-originated filters: update SupportTicketStatusFilter in
backend/lib/database/repositories/support_ticket_repository.dart lines 41-45 and
the TrialSessionStatus filter in
backend/lib/database/repositories/trial_repository.dart lines 55-60, preserving
the existing empty-status checks and filter construction while allowing
unsupported values to become validation failures.

Source: Learnings

backend/lib/database/repositories/user_repository.dart (1)

47-58: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not silently discard the caller-supplied ID.

UserRepository.create() and the deprecated DatabaseClient.createUser() still require id, but the user model has @default(cuid()) and this path ignores the parameter. If callers use the supplied ID for related accounts/profiles, those references can point to different generated user IDs. Remove id from this API and migrate callers, or restore an ID-capable creation path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/user_repository.dart` around lines 47 - 58,
Update UserRepository.create and deprecated DatabaseClient.createUser so their
public API no longer accepts an id, then migrate all callers to use the returned
user ID for related records. Ensure the Prisma creation path remains consistent
with CreateUserInput and never silently ignores a caller-supplied identifier.
backend/lib/database/repositories/collaborator_repository.dart (2)

88-105: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Make the pending check and response update atomic.

Two concurrent responses can both pass the PENDING check, then the last update(where: id) wins. Use a conditional update requiring id, consultantProfileId, and PENDING, and only report success when a row was affected.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/collaborator_repository.dart` around lines
88 - 105, Replace the separate pending lookup and unconditional update in the
collaborator response flow with one conditional update requiring id,
consultantProfileId, and CollaboratorStatus.pending. Use the update result to
return success only when a row was affected, preserving the existing response
status and respondedAt values.

156-172: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Complete the consolidated-model field mapping before shipping.

CollaboratorRepository.getMyCollaborations() still uses the TODO-acknowledged old field names in both flatteners, mapping renamed keys such as revenueShareBps and missing fields the generated Collaborator.toJson() no longer exposes. The list endpoint serializes these values, so keep the response contract intact by aligning both flatteners with the generated schema keys and removing the TODO.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/collaborator_repository.dart` around lines
156 - 172, Update both flatteners in
CollaboratorRepository.getMyCollaborations() to use the generated
Collaborator.toJson() schema keys, replacing outdated fields such as
revenueShareBps and adding any required renamed fields so list responses match
the consolidated model contract. Remove the associated TODO and preserve the
existing response structure and serialization behavior.
backend/lib/database/repositories/referral_repository.dart (2)

122-138: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle concurrent referral-code creation.

Two first-time requests can both observe no existing code, then one create fails on the unique user constraint. Use an upsert keyed by the user or catch the conflict and return the existing code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/referral_repository.dart` around lines 122
- 138, Update the referral-code creation flow after getReferralCode in the
repository method to handle concurrent requests safely: use an upsert keyed by
userId, or catch the unique-constraint failure from _prisma.referralCode.create
and return the existing referral code. Preserve returning the already-created
code rather than propagating the race-condition error.

48-84: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make referral-cap enforcement atomic.

totalReferrals is read before the transaction and then written as totalReferrals + 1. Concurrent requests can both pass the cap, create multiple referrals, and overwrite the counter. Perform a conditional atomic increment/recheck inside the transaction before creating the referral and credit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/referral_repository.dart` around lines 48 -
84, Move referral-cap validation from the pre-transaction read into the
_prisma.$transaction callback, and atomically recheck/update the ReferralCode
counter there using a conditional operation that only increments when
totalReferrals remains below maxReferrals. Create the Referral record and apply
the credit only after that update succeeds; remove reliance on the stale
totalReferrals value captured before the transaction while preserving the
existing max-cap error behavior.
backend/lib/database/repositories/payout_account_repository.dart (1)

23-41: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Enforce the single-default invariant across both write paths.

create(isDefault: true) does not unset an existing default, while setDefault clears defaults before verifying that id belongs to consultantProfileId. Concurrent calls can also interleave and leave multiple defaults. Validate ownership first, centralize the assignment transaction, and enforce the invariant at the database level if possible.

Also applies to: 97-109

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/database/repositories/payout_account_repository.dart` around
lines 23 - 41, Update the payout-account write flow around create and setDefault
to enforce one default per consultantProfileId. Validate the target account’s
ownership before modifying defaults, and centralize default assignment in a
transaction that clears the consultant’s existing default before setting the new
account, including create(isDefault: true). Add a database-level uniqueness
constraint or equivalent protection against concurrent assignments where
supported.
backend/lib/route_handlers/recordings_reserved_handlers.dart (1)

177-181: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve existing metadata when optional Stream fields are absent.

The update path turns missing url into '', missing duration into 0, and missing file_size into null, overwriting valid stored data during partial or replay syncs. Only update fields present in the payload; keep defaults for the create path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/route_handlers/recordings_reserved_handlers.dart` around lines
177 - 181, Update the recording update flow around UpdateRecordingInput so
absent Stream fields are omitted rather than converted to empty-string, zero, or
null values, preserving existing stored metadata during partial or replay syncs.
Use nullable field values or the update mechanism’s existing omission semantics
for recordingUrl, durationInMinutes, and fileSize; retain the current defaults
only in the create path.
backend/lib/services/auth/auth_service.dart (1)

134-174: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the legacy auth setup stubs from the signup test.

backend/test/services/auth/auth_service_test.dart now wires typed user, account, consulteeProfile, and preference delegates, but the signup test still configures executeInTransaction, createUser, createCredentials, and createConsulteeProfile through legacy/transaction executors. Those stubs should be removed so the test only covers the typed db.prisma.$transaction flow.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/services/auth/auth_service.dart` around lines 134 - 174, The
signup test should remove its legacy executeInTransaction, createUser,
createCredentials, and createConsulteeProfile stubs, retaining only typed
delegate setup used by db.prisma.$transaction. The flow in
backend/lib/services/auth/auth_service.dart lines 134-174 requires no direct
change; backend/lib/services/webhook_handlers.dart lines 211-216 likewise
requires no direct change.
backend/routes/api/slots/availability/weekly/[id]/index.dart (1)

8-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Keep the ownership lookup inside the repository layer.

The route now constructs db.prisma.slotOfAvailabilityWeekly.findFirst(...) with StringFilter(equals: id) directly at lines 52-55, while the same handler uses db.slots.updateWeeklySlot/deleteWeeklySlot through SlotRepository. Add or use a repository method for the ownership fetch, especially if deleting this route-level import would remove the type provider for these filters.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/routes/api/slots/availability/weekly/`[id]/index.dart at line 8, Move
the weekly slot ownership lookup out of the route handler and into
SlotRepository by adding or reusing a repository method that accepts the id and
returns the matching record. Update the handler to call that method instead of
constructing db.prisma.slotOfAvailabilityWeekly.findFirst with StringFilter
directly, then remove the route-level Prisma filter import if it is no longer
needed.
backend/lib/utils/professional_background_utils.dart (1)

24-41: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Convert the remaining professional_background_utils.dart mutations to typed Prisma delegates.

This file still builds WorkExperience, Education, and Certification create/delete mutations with JsonQueryBuilder() / txn.executeMutation(query), and the onboarding flow calls these methods. Replace them with typed methods such as _prisma.workExperience.create, .education.create, .certification.create, and deleteMany filters so the code does not leave raw-legacy mutation patterns behind.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lib/utils/professional_background_utils.dart` around lines 24 - 41,
Replace all JsonQueryBuilder/txn.executeMutation usage in the professional
background helpers with typed Prisma delegate operations: use
_prisma.workExperience.create and deleteMany, _prisma.education.create and
deleteMany, and _prisma.certification.create and deleteMany with equivalent data
and filters. Preserve the existing onboarding behavior, field values,
transaction usage, and deletion criteria while removing the raw legacy mutation
patterns.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lib/database/repositories/collaborator_repository.dart`:
- Around line 102-105: Update the collaborator response handling around
enumFromWire so only terminal accept/decline CollaboratorStatus values are
allowed; explicitly reject pending and every other non-terminal status with HTTP
400 before constructing the collaborator record, while preserving valid terminal
responses.

In `@backend/routes/api/checkout/index.dart`:
- Around line 544-550: Validate and parse appointmentType and paymentGateway at
the route layer before fetching plans, creating bookings, or calling
db.checkout.createPayment. Reject unsupported values with the existing
bad-request response, and pass the validated appointment type and payment
gateway into the downstream checkout flow instead of raw strings.

In `@backend/test/repositories/account_repository_test.dart`:
- Around line 33-41: Update the repository tests around findByUserAndProvider
and the credential lookup, password update, and OAuth/credential creation cases
to capture the arguments passed to the mocked account methods instead of using
only any(...) matchers. Assert the generated filter and input fields include the
expected userId, providerId, accountId, password, and OAuth token values, while
retaining the existing result assertions and invocation checks.

In `@backend/test/repositories/consultant_explore_repository_test.dart`:
- Around line 1-7: Remove the library-level `@Skip` annotation and its stale
migration message from the consultant explore repository test suite. Leave the
existing Typed Delegate mock setup using mockPrisma and delegates unchanged so
ConsultantExploreRepository tests run normally.

In `@backend/test/repositories/support_ticket_repository_test.dart`:
- Around line 4-8: Remove the unnecessary `hide RecordNotFoundException` clause
from the runtime_server.dart import in the test file, leaving the import
otherwise unchanged.

In `@backend/test/repositories/user_repository_test.dart`:
- Around line 11-51: Replace the local MockUserDelegate and buildUser
declarations with imports from the shared prisma_mocks helper, and remove any
now-unused related imports or fake declarations. Keep FakeJsonQuery local if
required by this test, while preserving any other fakes that remain
independently used.

In `@backend/test/services/webhook_handlers_test.dart`:
- Around line 1-7: Remove the suite-level `@Skip` in the payment webhook tests and
migrate their stubs from the raw QueryExecutor to the typed Prisma delegates,
following the patterns in test/helpers/prisma_mocks.dart. Update the affected
webhook test setup and assertions so payment status changes, booking
confirmation, refunds, and disputes remain covered through the typed delegate
API.

---

Outside diff comments:
In `@backend/lib/database/database_client.dart`:
- Around line 172-192: Remove the unused private static _buildSchema() method,
including its schema registration and aliasing logic. Keep initialize()’s direct
registerAllModels(schemaRegistry) setup unchanged.

In `@backend/lib/database/repositories/account_repository.dart`:
- Around line 63-85: The createOAuth and createCredentials method signatures
expose required id parameters that are never applied. Remove the unused id
parameter from both methods and update all callers, including the deprecated
DatabaseClient.createOAuthAccount passthrough, so account IDs continue to come
from schema defaults without implying caller control.

In `@backend/lib/database/repositories/appointment_repository.dart`:
- Around line 1092-1103: Standardize the TRIAL booking planDuration type across
_fetchConsultantTrialBookings, _fetchTrialBookings, and _getTrialById. Return
trialDurationMinutes using the same double conversion in _getTrialById as the
list-building paths, while leaving freeTrialDurationMinutes unchanged.

In `@backend/lib/database/repositories/collaborator_repository.dart`:
- Around line 88-105: Replace the separate pending lookup and unconditional
update in the collaborator response flow with one conditional update requiring
id, consultantProfileId, and CollaboratorStatus.pending. Use the update result
to return success only when a row was affected, preserving the existing response
status and respondedAt values.
- Around line 156-172: Update both flatteners in
CollaboratorRepository.getMyCollaborations() to use the generated
Collaborator.toJson() schema keys, replacing outdated fields such as
revenueShareBps and adding any required renamed fields so list responses match
the consolidated model contract. Remove the associated TODO and preserve the
existing response structure and serialization behavior.

In `@backend/lib/database/repositories/dispute_repository.dart`:
- Around line 129-142: Update updateDisputeStatus to convert the webhook-sourced
status with the existing enumFromWire() helper instead of
DisputeStatus.values.firstWhere. Preserve the current updateMany behavior while
ensuring unrecognized wire values follow enumFromWire()’s established graceful
failure behavior.

In `@backend/lib/database/repositories/payout_account_repository.dart`:
- Around line 23-41: Update the payout-account write flow around create and
setDefault to enforce one default per consultantProfileId. Validate the target
account’s ownership before modifying defaults, and centralize default assignment
in a transaction that clears the consultant’s existing default before setting
the new account, including create(isDefault: true). Add a database-level
uniqueness constraint or equivalent protection against concurrent assignments
where supported.

In `@backend/lib/database/repositories/referral_repository.dart`:
- Around line 122-138: Update the referral-code creation flow after
getReferralCode in the repository method to handle concurrent requests safely:
use an upsert keyed by userId, or catch the unique-constraint failure from
_prisma.referralCode.create and return the existing referral code. Preserve
returning the already-created code rather than propagating the race-condition
error.
- Around line 48-84: Move referral-cap validation from the pre-transaction read
into the _prisma.$transaction callback, and atomically recheck/update the
ReferralCode counter there using a conditional operation that only increments
when totalReferrals remains below maxReferrals. Create the Referral record and
apply the credit only after that update succeeds; remove reliance on the stale
totalReferrals value captured before the transaction while preserving the
existing max-cap error behavior.

In `@backend/lib/database/repositories/support_ticket_repository.dart`:
- Around line 41-45: Replace the throwing firstWhere status parsing with
enumFromWire for request-originated filters: update SupportTicketStatusFilter in
backend/lib/database/repositories/support_ticket_repository.dart lines 41-45 and
the TrialSessionStatus filter in
backend/lib/database/repositories/trial_repository.dart lines 55-60, preserving
the existing empty-status checks and filter construction while allowing
unsupported values to become validation failures.

In `@backend/lib/database/repositories/user_repository.dart`:
- Around line 47-58: Update UserRepository.create and deprecated
DatabaseClient.createUser so their public API no longer accepts an id, then
migrate all callers to use the returned user ID for related records. Ensure the
Prisma creation path remains consistent with CreateUserInput and never silently
ignores a caller-supplied identifier.

In `@backend/lib/route_handlers/recordings_reserved_handlers.dart`:
- Around line 177-181: Update the recording update flow around
UpdateRecordingInput so absent Stream fields are omitted rather than converted
to empty-string, zero, or null values, preserving existing stored metadata
during partial or replay syncs. Use nullable field values or the update
mechanism’s existing omission semantics for recordingUrl, durationInMinutes, and
fileSize; retain the current defaults only in the create path.

In `@backend/lib/services/auth/auth_service.dart`:
- Around line 134-174: The signup test should remove its legacy
executeInTransaction, createUser, createCredentials, and createConsulteeProfile
stubs, retaining only typed delegate setup used by db.prisma.$transaction. The
flow in backend/lib/services/auth/auth_service.dart lines 134-174 requires no
direct change; backend/lib/services/webhook_handlers.dart lines 211-216 likewise
requires no direct change.

In `@backend/lib/utils/professional_background_utils.dart`:
- Around line 24-41: Replace all JsonQueryBuilder/txn.executeMutation usage in
the professional background helpers with typed Prisma delegate operations: use
_prisma.workExperience.create and deleteMany, _prisma.education.create and
deleteMany, and _prisma.certification.create and deleteMany with equivalent data
and filters. Preserve the existing onboarding behavior, field values,
transaction usage, and deletion criteria while removing the raw legacy mutation
patterns.

In `@backend/routes/api/slots/availability/weekly/`[id]/index.dart:
- Line 8: Move the weekly slot ownership lookup out of the route handler and
into SlotRepository by adding or reusing a repository method that accepts the id
and returns the matching record. Update the handler to call that method instead
of constructing db.prisma.slotOfAvailabilityWeekly.findFirst with StringFilter
directly, then remove the route-level Prisma filter import if it is no longer
needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 841dc794-7dc0-448d-9597-71116fe2d390

📥 Commits

Reviewing files that changed from the base of the PR and between a2f7dfc and 3221de2.

📒 Files selected for processing (99)
  • backend/lib/database/database_client.dart
  • backend/lib/database/repositories/account_repository.dart
  • backend/lib/database/repositories/appointment_repository.dart
  • backend/lib/database/repositories/checkout_repository.dart
  • backend/lib/database/repositories/collaborator_repository.dart
  • backend/lib/database/repositories/consultant_explore_repository.dart
  • backend/lib/database/repositories/consultant_profile_repository.dart
  • backend/lib/database/repositories/consultee_profile_repository.dart
  • backend/lib/database/repositories/dashboard_repository.dart
  • backend/lib/database/repositories/dispute_repository.dart
  • backend/lib/database/repositories/payout_account_repository.dart
  • backend/lib/database/repositories/plan_repository.dart
  • backend/lib/database/repositories/programs_repository.dart
  • backend/lib/database/repositories/referral_repository.dart
  • backend/lib/database/repositories/refund_repository.dart
  • backend/lib/database/repositories/slot_repository.dart
  • backend/lib/database/repositories/support_ticket_repository.dart
  • backend/lib/database/repositories/trial_repository.dart
  • backend/lib/database/repositories/user_repository.dart
  • backend/lib/database/repositories/waitlist_repository.dart
  • backend/lib/database/repositories/webhook_event_repository.dart
  • backend/lib/route_handlers/recordings_reserved_handlers.dart
  • backend/lib/route_handlers/trials_reserved_handlers.dart
  • backend/lib/services/auth/auth_service.dart
  • backend/lib/services/webhook_handlers.dart
  • backend/lib/utils/enum_utils.dart
  • backend/lib/utils/professional_background_utils.dart
  • backend/routes/_middleware.dart
  • backend/routes/api/announcements/index.dart
  • backend/routes/api/appointments/[id]/cancel.dart
  • backend/routes/api/appointments/[id]/documents/[docId]/index.dart
  • backend/routes/api/appointments/[id]/documents/index.dart
  • backend/routes/api/auth/change-password.dart
  • backend/routes/api/auth/forgot-password.dart
  • backend/routes/api/auth/reset-password.dart
  • backend/routes/api/auth/revoke-other-sessions.dart
  • backend/routes/api/auth/revoke-session.dart
  • backend/routes/api/auth/set-password.dart
  • backend/routes/api/auth/verify-email.dart
  • backend/routes/api/checkout/index.dart
  • backend/routes/api/checkout/validate-discount.dart
  • backend/routes/api/checkout/verify.dart
  • backend/routes/api/collaborations/[id]/index.dart
  • backend/routes/api/collaborations/[id]/respond.dart
  • backend/routes/api/consultant/payout-accounts/index.dart
  • backend/routes/api/consultant/profile.dart
  • backend/routes/api/consultant/tax-info/index.dart
  • backend/routes/api/consultant/tds-records/index.dart
  • backend/routes/api/consultee/profile.dart
  • backend/routes/api/dashboard/consultant/[consultantId]/index.dart
  • backend/routes/api/dashboard/consultee/[consulteeId]/index.dart
  • backend/routes/api/domains/[id]/index.dart
  • backend/routes/api/onboarding/submit.dart
  • backend/routes/api/payments/discounts/validate.dart
  • backend/routes/api/plans/classes/[id]/index.dart
  • backend/routes/api/plans/classes/index.dart
  • backend/routes/api/plans/consultations/[id]/index.dart
  • backend/routes/api/plans/consultations/index.dart
  • backend/routes/api/plans/subscriptions/[id]/index.dart
  • backend/routes/api/plans/subscriptions/index.dart
  • backend/routes/api/plans/webinars/[id]/index.dart
  • backend/routes/api/plans/webinars/index.dart
  • backend/routes/api/slots/availability/custom/[id]/index.dart
  • backend/routes/api/slots/availability/custom/index.dart
  • backend/routes/api/slots/availability/weekly/[id]/index.dart
  • backend/routes/api/slots/availability/weekly/index.dart
  • backend/routes/api/staff/feedbacks/index.dart
  • backend/routes/api/staff/stats.dart
  • backend/routes/api/staff/support-tickets/[ticketId]/index.dart
  • backend/routes/api/staff/support-tickets/index.dart
  • backend/routes/api/stream/add-member/index.dart
  • backend/routes/api/stream/create-group-channel/index.dart
  • backend/routes/api/stream/fix-group-channels/index.dart
  • backend/routes/api/stream/recordings/[id]/index.dart
  • backend/routes/api/support/[ticketId]/attachments.dart
  • backend/routes/api/tags/index.dart
  • backend/routes/api/topics/index.dart
  • backend/routes/api/trials/[trialId]/index.dart
  • backend/routes/api/trials/index.dart
  • backend/routes/api/user/[id]/index.dart
  • backend/routes/api/user/[id]/professional-background/index.dart
  • backend/routes/api/waitlist/[id]/index.dart
  • backend/routes/api/waitlist/index.dart
  • backend/test/helpers/prisma_mocks.dart
  • backend/test/repositories/account_repository_test.dart
  • backend/test/repositories/appointment_repository_test.dart
  • backend/test/repositories/checkout_repository_test.dart
  • backend/test/repositories/consultant_explore_repository_test.dart
  • backend/test/repositories/session_repository_test.dart
  • backend/test/repositories/support_ticket_repository_test.dart
  • backend/test/repositories/user_repository_test.dart
  • backend/test/repositories/verification_repository_test.dart
  • backend/test/routes/appointments/index_test.dart
  • backend/test/routes/checkout/verify_test.dart
  • backend/test/services/auth/auth_service_test.dart
  • backend/test/services/email_service_test.dart
  • backend/test/services/profile_service_test.dart
  • backend/test/services/webhook_handlers_test.dart
  • backend/test/utils/pan_crypto_test.dart

Comment thread backend/lib/database/repositories/collaborator_repository.dart Outdated
Comment thread backend/routes/api/checkout/index.dart
Comment thread backend/test/repositories/account_repository_test.dart Outdated
Comment thread backend/test/repositories/consultant_explore_repository_test.dart Outdated
Comment thread backend/test/repositories/support_ticket_repository_test.dart
Comment thread backend/test/repositories/user_repository_test.dart Outdated
Comment thread backend/test/services/webhook_handlers_test.dart Outdated
Kaustav Ghosh and others added 2 commits July 24, 2026 23:54
…enum defects they caught

266 passing (was 227), 3 suites still skipped.

- appointment_repository_test (15): typed delegate stubs for the plan/count
  duplicate-booking checks, the _getConsultantAppointmentIds -> slot overlap
  count path, and the consultee/consultant profile projections.
- checkout_repository_test (24): payment create/findUnique/update, plan and
  booking findUnique (with include), discount findFirst, slot updateMany.

Two real defects the repaired suites surfaced:
- checkout.validateDiscountCode cast maxDiscount 'as num?', but it is a BigInt
  column that toJson() serializes as a String -> TypeError on every discount
  code with a cap set. Now parsed across num/BigInt/String.
- checkout updatePaymentStatus and updateBookingStatus had unguarded
  client-input enum mappings -> enumFromWire (400, not a 500). The test also
  used PaymentStatus 'COMPLETED', which is not a valid value (PENDING,
  SUCCEEDED, FAILED, EXPIRED); corrected to SUCCEEDED.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… passing, 0 skipped)

Completes the test migration started after the JQB->typed-delegate work:

- consultant_explore_repository_test (14): count + findManyProjected /
  findFirstProjected stubs; getReviews reviewer resolution split across the
  consulteeProfile and user delegates; consultantReview.aggregate stubbed.
- webhook_handlers_test (16): db.prisma wired with payment/appointment/
  consultantProfile delegates; payment fixtures now Payment models.
- checkout/verify_test (11): payment findUnique + appointment/slot defaults.
  Two fixtures had to be corrected to match what the route actually enforces
  — the payment must belong to the authenticated user (userId 'user-123'),
  and the Razorpay-params 400 path requires paymentGateway RAZORPAY (the
  builder defaults to Stripe).

Final state: 307 passing, 0 failing, 0 skipped; dart analyze clean across
lib/routes/test; jqb-gate still 0/0. Server boots and the key endpoints
(tags/domains/consultants/classes/dashboard/appointments/me-organization)
all return 200.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Kaustav Ghosh and others added 6 commits July 25, 2026 00:25
No CI job ran the backend suite, which is why 75 tests sat broken and unnoticed
after the JQB->typed-delegate migration (and why the null->404, BigInt-cast and
unguarded-enum defects only surfaced when the tests were repaired by hand).

- New .github/workflows/backend-ci.yml (path-filtered to backend/**):
  regenerate -> analyze -> format -> test -> jqb-gate.
  * analyze: `dart analyze` exits non-zero on ANY issue including ~1k
    pre-existing style infos, so the step greps for error/warning lines and
    fails only on those.
  * format: gated over `git ls-files '*.dart'`, which lists tracked files only
    and therefore excludes the gitignored generated client for free.
  * jqb-gate: enforces the terminal 0/0 ratchet so raw JsonQueryBuilder or the
    removed raw finders cannot creep back.
- flutter-ci.yml only triggered on [main, develop], but this repo's default and
  base branch is `dev` — so the Flutter workflow never ran on day-to-day pushes
  or PRs. Added `dev` to both triggers.
- New backend/scripts/regenerate-build.sh: the database_client docs already
  told contributors to run it (a doc/reality mismatch inherited from the dev
  merge) but it did not exist. Regenerates the Prisma client + freezed; CI uses
  the same entry point. Verified end-to-end locally.
- analysis_options.yaml: exclude lib/generated/** and *.freezed/*.g.dart —
  gitignored, never hand-edited, and 19,199 of the 20,200 analyzer infos came
  from them.

Verified locally with the exact CI commands: analyze 0 errors/0 warnings,
format clean over 221 tracked files, 307 tests passing, jqb-gate 0/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First Backend CI run failed at pub resolution: prisma_flutter_connector depends
on the Flutter SDK, so a plain-Dart runner cannot resolve it ('the Flutter SDK
is not available', exit 69) even though the backend is server-side Dart.

- Workflow now sets up Flutter (subosito/flutter-action) instead of Dart.
- regenerate-build.sh picks `flutter pub` when Flutter is on PATH, falling
  back to `dart pub` otherwise.

Follow-up worth considering in the connector: split a pure-Dart core out of
prisma_flutter_connector so server-side consumers do not need Flutter at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-enabling the Flutter workflow on `dev` exposed that its pin was long dead:
FLUTTER_VERSION 3.24.3 ships Dart 3.5.3, but the dependency set now requires
Dart >=3.7 — the app fails on `skeletonizer ^2.1.3` and the backend on
`bcrypt >=1.2.0`. Both jobs failed at `pub get` on their first real run.

- flutter-ci.yml + backend-ci.yml: pin Flutter 3.44.1 (Dart 3.12), matching the
  toolchain this branch was developed and verified against.
- pubspec.yaml / backend/pubspec.yaml: raise the declared SDK floor from 3.5.0
  to 3.7.0. The old constraints understated reality — transitive deps already
  demanded 3.7, so a contributor on 3.5 hit a confusing resolution error rather
  than a clear "SDK too old".

Verified locally on Dart 3.12: both packages resolve, backend 307 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raising the declared floor to 3.7 changes the *language version*, which
switches `dart format` to the new tall-style formatter — CI reformatted 151 of
221 backend files, failing the format gate against a tree formatted in the old
style.

The floor bump was not what unblocked CI (the stale Flutter 3.24.3 pin was), so
revert it here to keep this PR reviewable rather than bury it under a
whitespace-only reformat of the whole backend.

The constraints are still understated (transitive deps require Dart >=3.7) —
correcting them plus the wholesale reformat belongs in its own focused PR where
the diff is the point.

Verified locally: format clean over 221 tracked files, 0 analyzer
errors/warnings, 307 tests passing, jqb-gate 0/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first real run of the mobile `Analyze & Test` job failed with ~19k phantom
errors from `backend/**` — a separate dart_frog package with its own pubspec,
analysis_options and (now) CI job, whose dependencies are not resolved in the
Flutter workspace and whose Prisma client is generated at build time.

Also exclude `build/**`: gitignored build output that carries third-party
SPM/CocoaPods checkouts (e.g. build/ios/SourcePackages/stream_video_flutter/
example) with their own unresolved example apps — 46 more phantom errors.

Both were always wrong; they only surfaced now because the workflow had never
actually run (its triggers pointed at branches this repo does not use).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Isolated, app-only cleanup — no behavior change. Safe to drop this commit if
you would rather it land separately from the backend migration.

Turning the Flutter workflow back on surfaced 52 accumulated lint issues (0
errors once codegen runs; the 8 "errors" locally were a stale .g.dart). Cleared
the unambiguous ones, 52 -> 10:

- `dart fix --apply`: 23 fixes across 12 files (unused/unnecessary imports,
  unnecessary null checks and non-null assertions, prefer_final_fields,
  withOpacity -> withValues, curly braces).
- user_model.dart: `ignore_for_file: invalid_annotation_target` (15 issues) —
  @jsonkey on a freezed constructor param is the documented pattern; the
  annotation lands on the generated field. Same suppression the Prisma client
  generator emits.
- app_theme.dart: two unused palette tokens kept (design-token parity with web)
  and marked intentional.
- dio_client.dart: escaped `Map<String, dynamic>` in doc comments.

Deliberately NOT fixed — these need judgement, not a sweep:
- 6x Radio groupValue/onChanged: needs a real RadioGroup migration with UI
  behaviour risk.
- 2x Sentry setExtra -> Contexts.
- 1x use_build_context_synchronously: needs a considered mounted check.
- 1x experimental_member_use (Sentry attachViewHierarchy): intentional.

flutter test: 104 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
backend/test/repositories/checkout_repository_test.dart (1)

528-544: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Stale mockExecutor verification no longer proves anything.

This test intends to prove "no slot update occurs when no appointment is found," but it verifies mockExecutor.executeMutation was never called — a path that confirmSlots no longer uses at all (it now calls mockSlots.updateMany). The assertion is now a tautology and won't catch a regression where slots get incorrectly updated on a null-appointment path.

🐛 Proposed fix
       // Should not attempt to update slots
-      verifyNever(() => mockExecutor.executeMutation(any()));
+      verifyNever(
+        () => mockSlots.updateMany(
+          where: any(named: 'where'),
+          data: any(named: 'data'),
+        ),
+      );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/test/repositories/checkout_repository_test.dart` around lines 528 -
544, Update the no-appointment test to verify that mockSlots.updateMany is never
called instead of checking mockExecutor.executeMutation. Keep the existing null
appointment setup and successful confirmSlots expectation unchanged, so the test
specifically guards against slot updates on this path.
backend/test/repositories/consultant_explore_repository_test.dart (1)

264-306: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Filter tests no longer verify the filter is actually applied.

applies domain filter, applies search query, and applies minimum rating filter all match findManyProjected/count with where: any(named: 'where') and only assert on unrelated output (e.g., priceCurrency default) or call counts. None of them capture/inspect the actual where value, so a broken domain/search/rating filter translation would not be caught by these tests.

Consider using captureAny(named: 'where') (via verify(...).captured) to assert the constructed filter actually contains the expected domainId/search term/rating condition.

Also applies to: 308-339, 341-361

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/test/repositories/consultant_explore_repository_test.dart` around
lines 264 - 306, Update the tests named applies domain filter, applies search
query, and applies minimum rating filter to capture the actual where argument
from findManyProjected and count using verify(...).captured, then assert it
contains the expected domainId, search term, or rating condition. Keep the
existing result and call-count assertions where useful, but remove reliance on
where: any(...) as the only filter verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/backend-ci.yml:
- Around line 1-32: Harden the backend workflow by pinning both checkout and
Flutter action references to immutable commit SHAs, setting checkout’s
persist-credentials option to false, and adding a top-level permissions block
with only the required access (default read-only if no writes are needed). Add a
concurrency group for backend workflow runs that cancels superseded runs, using
the workflow or pull-request context to avoid unrelated runs sharing a group.
- Around line 42-51: Update the “Analyze (errors + warnings)” workflow step to
invoke dart analyze with JSON output and parse the resulting structured
diagnostics instead of grepping human-readable analyze.log prefixes. Preserve
the gate’s behavior of failing when any error or warning is reported, while
keeping the diagnostic output available in the CI log.

---

Outside diff comments:
In `@backend/test/repositories/checkout_repository_test.dart`:
- Around line 528-544: Update the no-appointment test to verify that
mockSlots.updateMany is never called instead of checking
mockExecutor.executeMutation. Keep the existing null appointment setup and
successful confirmSlots expectation unchanged, so the test specifically guards
against slot updates on this path.

In `@backend/test/repositories/consultant_explore_repository_test.dart`:
- Around line 264-306: Update the tests named applies domain filter, applies
search query, and applies minimum rating filter to capture the actual where
argument from findManyProjected and count using verify(...).captured, then
assert it contains the expected domainId, search term, or rating condition. Keep
the existing result and call-count assertions where useful, but remove reliance
on where: any(...) as the only filter verification.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e9438219-9e4a-4c07-ab74-a272c7c3e9ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3221de2 and b8b7919.

📒 Files selected for processing (11)
  • .github/workflows/backend-ci.yml
  • .github/workflows/flutter-ci.yml
  • backend/analysis_options.yaml
  • backend/lib/database/repositories/checkout_repository.dart
  • backend/scripts/regenerate-build.sh
  • backend/test/helpers/prisma_mocks.dart
  • backend/test/repositories/appointment_repository_test.dart
  • backend/test/repositories/checkout_repository_test.dart
  • backend/test/repositories/consultant_explore_repository_test.dart
  • backend/test/routes/checkout/verify_test.dart
  • backend/test/services/webhook_handlers_test.dart

Comment thread .github/workflows/backend-ci.yml
Comment thread .github/workflows/backend-ci.yml Outdated
Kaustav Ghosh and others added 3 commits July 25, 2026 13:58
CodeRabbit flagged the grep-based analyze gate as fragile. It was worse than
fragile: `dart analyze --format=json` reports 76 WARNINGs that the
human-readable output labels as info, so the grep passed cleanly while real
warnings sat in the tree. Several were introduced by this branch.

Gate (now severity-accurate):
- Parse `dart analyze --format=json` and fail on ERROR/WARNING, emitting GitHub
  annotations per finding.
- Harden the workflow per the same review: `permissions: contents: read`,
  `persist-credentials: false`, actions pinned to commit SHAs,
  `concurrency` with cancel-in-progress, `timeout-minutes`.

Warnings it exposed — mine:
- auth_service: `linkedUser ?? newUser` was dead (typed update returns
  non-null) ×2; the GitHub branch's `name != null` guard is always true because
  name falls back to the login — made unconditional, behaviour preserved.
- database_client: dead `_buildSchema()` left behind by the dev merge; missing
  type arg on `pg.Pool.withEndpoints`.
- user_reserved_handlers / onboarding: imports left unused after the JQB
  conversions.
- fix-group-channels: `instructor.name != null` / `participant.name != null`
  are dead (User.name is non-null).
Pre-existing: unused `_months`, untyped placeholder lists, redundant `!` in
stripe_service and webhook_handlers, stray imports and an unused mock in tests.
The 51 inference/raw-type findings in the mock helpers are suppressed per-file
with rationale — the generated delegates share no base class, so the shared
stub helpers must take `dynamic`.

Also from the review:
- collaborator respondToCollaboration: `response` now only accepts ACCEPTED or
  DECLINED. enumFromWire alone also accepted PENDING/REMOVED, letting a client
  reopen or silently remove a collaboration.
- checkout route: validate `appointmentType` and `paymentGateway` before any DB
  work (400 with the allowed set) and use the normalised values downstream,
  instead of relying on an ArgumentError from a later repository call.
- account_repository_test: assert captured filters/inputs (userId, providerId,
  accountId, password, OAuth tokens) rather than `any(...)` — the tests would
  previously have passed with no filters at all.
- user_repository_test: use the shared prisma_mocks helpers instead of
  duplicating the mocks, fakes and buildUser.

Not applied: the suggestion to drop `hide RecordNotFoundException` from
support_ticket_repository_test — verified it is required, the name is exported
by both the connector runtime and the repository.

Backend-wide: 0 errors/warnings, format clean (221 files), 307 tests, gate 0/0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
App analyze is now clean under --fatal-infos (was 10 issues), and the CI
actions are back on readable major tags.

Radio -> RadioGroup (Flutter 3.35 radio-api-redesign, 6 issues). Followed the
official migration: RadioGroup<T> owns groupValue/onChanged and wraps the
related radios via `child:`; each Radio keeps only `value`.
- payment_method_selector: RadioGroup wraps the gateway Column.
- preferences_step: the BudgetPreference spread became a RadioGroup-wrapped
  Column so the group is scoped to the budget radios, not the whole form.
- role_selection_card: RoleSelector owns the group; this also removes the
  `groupValue: isSelected ? role : null` hack the old API forced.

Sentry setExtra -> setContexts (2 issues). Verified against the installed SDK
(sentry 9.25.0) that Scope.setExtra is @deprecated and setContexts(String,
dynamic) is the replacement; the extras map now goes in under one structured
context key instead of N extras.

use_build_context_synchronously (1 issue) — a real bug, not a style nit:
_handleReschedule awaited a bottom sheet and then reused `context` for a second
sheet with no mounted check. Added `if (!mounted) return;` after the gap.

experimental_member_use (1 issue): documented ignore — attachViewHierarchy is
only ever set to false, so nothing depends on the API shape.

Actions: reverted the commit-SHA pins to actions/checkout@v4 and
subosito/flutter-action@v2 per review preference, and moved the Flutter version
into env.FLUTTER_VERSION mirroring flutter-ci.yml so the two workflows can't
drift apart.

flutter analyze --fatal-infos: clean. flutter test: 104 passing.
Backend unchanged: 0 errors/warnings, 307 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A dart format pass wrapped `if (dateOfBirth != null) data[...] = ...` across
two lines, which trips curly_braces_in_flow_control_structures. Braced it.

flutter analyze --fatal-infos: clean. flutter test: 104 passing.
@teetangh
teetangh merged commit bcac99c into dev Jul 25, 2026
7 checks passed
@teetangh
teetangh deleted the feat/retire-jqb-mega-sync branch July 25, 2026 09:57
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