feat(backend): retire JsonQueryBuilder — full typed-delegate migration + web-schema re-sync (257→4 JQB, 60→0 raw) - #122
Conversation
…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>
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe 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. ChangesTyped Prisma migration
Enterprise schema, encryption, and tooling
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
There was a problem hiding this comment.
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 winPrefer certificate verification for the hosted PostgreSQL path.
pg.SslMode.requireenables TLS without verifying the server certificate/hostname, so the Supabase-hosted path can be MITM-prone. If the database exposes a verifiable certificate, usepg.SslMode.verifyFull; ifrequireis 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 winPreserve not-found → null in
updateStatus.
updateStatusreturnsFuture<Map<String, dynamic>?>, and the trial update route treatsnullas no change. A Prismaupdatefor a missing row should returnnullhere rather than throw; add afindUniquepre-check before callingtrialSession.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 winNarrow the create error handling instead of treating every failure as a duplicate.
Enum mapping uses unguarded
firstWhereinside thetry, so an invalidcurrency,status, orpaymentGatewaythrows before_prisma.refund.create, then the broadcatchlogs"possibly duplicate"and returns the absent refund. A non-duplicate DB failure is also treated the same way. Move enum mapping/validation outside thetry, 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
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (65)
backend/lib/database/database_client.dartbackend/lib/database/repositories/account_repository.dartbackend/lib/database/repositories/announcement_repository.dartbackend/lib/database/repositories/appointment_document_repository.dartbackend/lib/database/repositories/appointment_repository.dartbackend/lib/database/repositories/checkout_repository.dartbackend/lib/database/repositories/collaborator_repository.dartbackend/lib/database/repositories/consultant_explore_repository.dartbackend/lib/database/repositories/consultant_profile_repository.dartbackend/lib/database/repositories/consultant_verification_repository.dartbackend/lib/database/repositories/consultee_profile_repository.dartbackend/lib/database/repositories/dashboard_repository.dartbackend/lib/database/repositories/dispute_repository.dartbackend/lib/database/repositories/domain_repository.dartbackend/lib/database/repositories/maintenance_repository.dartbackend/lib/database/repositories/meeting_session_repository.dartbackend/lib/database/repositories/payout_account_repository.dartbackend/lib/database/repositories/plan_repository.dartbackend/lib/database/repositories/programs_repository.dartbackend/lib/database/repositories/referral_repository.dartbackend/lib/database/repositories/refund_repository.dartbackend/lib/database/repositories/review_repository.dartbackend/lib/database/repositories/session_repository.dartbackend/lib/database/repositories/slot_repository.dartbackend/lib/database/repositories/support_ticket_repository.dartbackend/lib/database/repositories/trial_repository.dartbackend/lib/database/repositories/user_repository.dartbackend/lib/database/repositories/verification_repository.dartbackend/lib/database/repositories/waitlist_repository.dartbackend/lib/route_handlers/recordings_reserved_handlers.dartbackend/lib/route_handlers/user_reserved_handlers.dartbackend/lib/services/auth/auth_service.dartbackend/lib/services/webhook_handlers.dartbackend/prisma/schema.prismabackend/pubspec.yamlbackend/routes/api/appointments/[id]/documents/[docId]/index.dartbackend/routes/api/appointments/[id]/documents/index.dartbackend/routes/api/checkout/index.dartbackend/routes/api/checkout/verify.dartbackend/routes/api/collaborations/[id]/index.dartbackend/routes/api/consultant/tax-info/index.dartbackend/routes/api/consultant/tds-records/index.dartbackend/routes/api/consultants/[id]/availability.dartbackend/routes/api/dashboard/consultant/[consultantId]/index.dartbackend/routes/api/dashboard/consultee/[consulteeId]/index.dartbackend/routes/api/domains/[id]/index.dartbackend/routes/api/invoices/[id]/index.dartbackend/routes/api/payments/discounts/validate.dartbackend/routes/api/slots/availability/custom/[id]/index.dartbackend/routes/api/slots/availability/weekly/[id]/index.dartbackend/routes/api/staff/feedbacks/[feedbackId]/index.dartbackend/routes/api/staff/feedbacks/index.dartbackend/routes/api/staff/moderation/profiles/[verificationId]/index.dartbackend/routes/api/staff/moderation/profiles/index.dartbackend/routes/api/staff/stats.dartbackend/routes/api/staff/support-tickets/[ticketId]/index.dartbackend/routes/api/staff/support-tickets/[ticketId]/responses.dartbackend/routes/api/staff/support-tickets/index.dartbackend/routes/api/stream/fix-group-channels/index.dartbackend/routes/api/support/[ticketId]/attachments.dartbackend/routes/api/tags/index.dartbackend/routes/api/topics/index.dartbackend/routes/api/user/[id]/professional-background/index.dartbackend/scripts/jqb-gate.shpubspec.yaml
…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>
There was a problem hiding this comment.
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 winMake the ratchet fail closed and count matches, not lines.
grep | wc -lcounts matching lines rather than call sites, while2>/dev/null || trueconverts 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
⛔ Files ignored due to path filters (1)
pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
backend/lib/database/database_client.dartbackend/lib/database/repositories/appointment_repository.dartbackend/lib/database/repositories/consultant_explore_repository.dartbackend/lib/database/repositories/consultant_profile_repository.dartbackend/lib/database/repositories/dashboard_repository.dartbackend/lib/database/repositories/organization_repository.dartbackend/lib/database/repositories/plan_repository.dartbackend/lib/route_handlers/user_reserved_handlers.dartbackend/lib/services/stream_service.dartbackend/pubspec.yamlbackend/routes/api/stream/fix-group-channels/index.dartbackend/scripts/jqb-gate.shpubspec.yaml
…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>
There was a problem hiding this comment.
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 valueDrop the unreachable
linkedUser ?? newUserfallback.
tx.user.update()is typed as returningFuture<User>, so both OAuth branches can mirror the email path and returnlinkedUser.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
📒 Files selected for processing (17)
backend/lib/database/repositories/appointment_repository.dartbackend/lib/database/repositories/programs_repository.dartbackend/lib/route_handlers/recordings_reserved_handlers.dartbackend/lib/services/auth/auth_service.dartbackend/lib/utils/pan_crypto.dartbackend/pubspec.yamlbackend/routes/api/appointments/[id]/documents/[docId]/index.dartbackend/routes/api/appointments/[id]/documents/index.dartbackend/routes/api/checkout/verify.dartbackend/routes/api/consultant/tax-info/index.dartbackend/routes/api/domains/[id]/index.dartbackend/routes/api/slots/availability/custom/[id]/index.dartbackend/routes/api/staff/feedbacks/[feedbackId]/index.dartbackend/routes/api/staff/support-tickets/[ticketId]/index.dartbackend/routes/api/staff/support-tickets/index.dartbackend/routes/api/tags/index.dartbackend/test/utils/pan_crypto_test.dart
💤 Files with no reviewable changes (1)
- backend/lib/database/repositories/programs_repository.dart
…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>
There was a problem hiding this comment.
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
updateDisputeStatusstill 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 toenumFromWire()to avoid an uncaughtStateErroron unrecognized wire values.updateDisputeStatusstill uses rawfirstWherewithout a guard, so an unexpectedstatusfrom a payment-gateway webhook will throwStateErrorhere 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 uncaughtStateError.🤖 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 winInconsistent
planDurationtype for TRIAL bookings across list vs. detail responses.
_fetchConsultantTrialBookings(Line 1102) and_fetchTrialBookings(Line 1539) both casttrialDurationMinutestodoubleforplanDuration, but_getTrialById(Line 2248) returns the raw value uncast. SincetrialDurationMinutesis an Int column,getMyBookings(list) returns adoublewhilegetBookingById(detail) returns anintfor the same field on the same booking type — a client that expects one Dart type viaas double/as intwill 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 winUnused required
idparameter is misleading.Both
createOAuthandcreateCredentialsdeclarerequired String idbut never use it — the row's id is autofilled by the schema default. A caller (including the deprecatedDatabaseClient.createOAuthAccountpassthrough) could reasonably assume the suppliedidbecomes 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 valueRemove unused
_buildSchema().
initialize()now populatesschemaRegistrydirectly withregisterAllModels(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 winUse
enumFromWirefor raw status filters.Unsupported status values currently throw
StateErrorviafirstWhere, so these request-originated filters can return 500 instead of validation failures.
backend/lib/database/repositories/support_ticket_repository.dart#L43-L45: replaceSupportTicketStatus.values.firstWhere(...)withenumFromWire(...).backend/lib/database/repositories/trial_repository.dart#L19-L20: replaceTrialSessionStatus.values.firstWhere(...)withenumFromWire(...).🤖 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 liftDo not silently discard the caller-supplied ID.
UserRepository.create()and the deprecatedDatabaseClient.createUser()still requireid, 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. Removeidfrom 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 winMake the pending check and response update atomic.
Two concurrent responses can both pass the
PENDINGcheck, then the lastupdate(where: id)wins. Use a conditional update requiringid,consultantProfileId, andPENDING, 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 winComplete the consolidated-model field mapping before shipping.
CollaboratorRepository.getMyCollaborations()still uses the TODO-acknowledged old field names in both flatteners, mapping renamed keys such asrevenueShareBpsand missing fields the generatedCollaborator.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 winHandle concurrent referral-code creation.
Two first-time requests can both observe no existing code, then one
createfails 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 liftMake referral-cap enforcement atomic.
totalReferralsis read before the transaction and then written astotalReferrals + 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 liftEnforce the single-default invariant across both write paths.
create(isDefault: true)does not unset an existing default, whilesetDefaultclears defaults before verifying thatidbelongs toconsultantProfileId. 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 winPreserve existing metadata when optional Stream fields are absent.
The update path turns missing
urlinto'', missing duration into0, and missingfile_sizeinto 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 winRemove the legacy auth setup stubs from the signup test.
backend/test/services/auth/auth_service_test.dartnow wires typeduser,account,consulteeProfile, and preference delegates, but the signup test still configuresexecuteInTransaction,createUser,createCredentials, andcreateConsulteeProfilethrough legacy/transaction executors. Those stubs should be removed so the test only covers the typeddb.prisma.$transactionflow.🤖 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 valueKeep the ownership lookup inside the repository layer.
The route now constructs
db.prisma.slotOfAvailabilityWeekly.findFirst(...)withStringFilter(equals: id)directly at lines 52-55, while the same handler usesdb.slots.updateWeeklySlot/deleteWeeklySlotthroughSlotRepository. 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 liftConvert the remaining
professional_background_utils.dartmutations to typed Prisma delegates.This file still builds
WorkExperience,Education, andCertificationcreate/delete mutations withJsonQueryBuilder()/txn.executeMutation(query), and the onboarding flow calls these methods. Replace them with typed methods such as_prisma.workExperience.create,.education.create,.certification.create, anddeleteManyfilters 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
📒 Files selected for processing (99)
backend/lib/database/database_client.dartbackend/lib/database/repositories/account_repository.dartbackend/lib/database/repositories/appointment_repository.dartbackend/lib/database/repositories/checkout_repository.dartbackend/lib/database/repositories/collaborator_repository.dartbackend/lib/database/repositories/consultant_explore_repository.dartbackend/lib/database/repositories/consultant_profile_repository.dartbackend/lib/database/repositories/consultee_profile_repository.dartbackend/lib/database/repositories/dashboard_repository.dartbackend/lib/database/repositories/dispute_repository.dartbackend/lib/database/repositories/payout_account_repository.dartbackend/lib/database/repositories/plan_repository.dartbackend/lib/database/repositories/programs_repository.dartbackend/lib/database/repositories/referral_repository.dartbackend/lib/database/repositories/refund_repository.dartbackend/lib/database/repositories/slot_repository.dartbackend/lib/database/repositories/support_ticket_repository.dartbackend/lib/database/repositories/trial_repository.dartbackend/lib/database/repositories/user_repository.dartbackend/lib/database/repositories/waitlist_repository.dartbackend/lib/database/repositories/webhook_event_repository.dartbackend/lib/route_handlers/recordings_reserved_handlers.dartbackend/lib/route_handlers/trials_reserved_handlers.dartbackend/lib/services/auth/auth_service.dartbackend/lib/services/webhook_handlers.dartbackend/lib/utils/enum_utils.dartbackend/lib/utils/professional_background_utils.dartbackend/routes/_middleware.dartbackend/routes/api/announcements/index.dartbackend/routes/api/appointments/[id]/cancel.dartbackend/routes/api/appointments/[id]/documents/[docId]/index.dartbackend/routes/api/appointments/[id]/documents/index.dartbackend/routes/api/auth/change-password.dartbackend/routes/api/auth/forgot-password.dartbackend/routes/api/auth/reset-password.dartbackend/routes/api/auth/revoke-other-sessions.dartbackend/routes/api/auth/revoke-session.dartbackend/routes/api/auth/set-password.dartbackend/routes/api/auth/verify-email.dartbackend/routes/api/checkout/index.dartbackend/routes/api/checkout/validate-discount.dartbackend/routes/api/checkout/verify.dartbackend/routes/api/collaborations/[id]/index.dartbackend/routes/api/collaborations/[id]/respond.dartbackend/routes/api/consultant/payout-accounts/index.dartbackend/routes/api/consultant/profile.dartbackend/routes/api/consultant/tax-info/index.dartbackend/routes/api/consultant/tds-records/index.dartbackend/routes/api/consultee/profile.dartbackend/routes/api/dashboard/consultant/[consultantId]/index.dartbackend/routes/api/dashboard/consultee/[consulteeId]/index.dartbackend/routes/api/domains/[id]/index.dartbackend/routes/api/onboarding/submit.dartbackend/routes/api/payments/discounts/validate.dartbackend/routes/api/plans/classes/[id]/index.dartbackend/routes/api/plans/classes/index.dartbackend/routes/api/plans/consultations/[id]/index.dartbackend/routes/api/plans/consultations/index.dartbackend/routes/api/plans/subscriptions/[id]/index.dartbackend/routes/api/plans/subscriptions/index.dartbackend/routes/api/plans/webinars/[id]/index.dartbackend/routes/api/plans/webinars/index.dartbackend/routes/api/slots/availability/custom/[id]/index.dartbackend/routes/api/slots/availability/custom/index.dartbackend/routes/api/slots/availability/weekly/[id]/index.dartbackend/routes/api/slots/availability/weekly/index.dartbackend/routes/api/staff/feedbacks/index.dartbackend/routes/api/staff/stats.dartbackend/routes/api/staff/support-tickets/[ticketId]/index.dartbackend/routes/api/staff/support-tickets/index.dartbackend/routes/api/stream/add-member/index.dartbackend/routes/api/stream/create-group-channel/index.dartbackend/routes/api/stream/fix-group-channels/index.dartbackend/routes/api/stream/recordings/[id]/index.dartbackend/routes/api/support/[ticketId]/attachments.dartbackend/routes/api/tags/index.dartbackend/routes/api/topics/index.dartbackend/routes/api/trials/[trialId]/index.dartbackend/routes/api/trials/index.dartbackend/routes/api/user/[id]/index.dartbackend/routes/api/user/[id]/professional-background/index.dartbackend/routes/api/waitlist/[id]/index.dartbackend/routes/api/waitlist/index.dartbackend/test/helpers/prisma_mocks.dartbackend/test/repositories/account_repository_test.dartbackend/test/repositories/appointment_repository_test.dartbackend/test/repositories/checkout_repository_test.dartbackend/test/repositories/consultant_explore_repository_test.dartbackend/test/repositories/session_repository_test.dartbackend/test/repositories/support_ticket_repository_test.dartbackend/test/repositories/user_repository_test.dartbackend/test/repositories/verification_repository_test.dartbackend/test/routes/appointments/index_test.dartbackend/test/routes/checkout/verify_test.dartbackend/test/services/auth/auth_service_test.dartbackend/test/services/email_service_test.dartbackend/test/services/profile_service_test.dartbackend/test/services/webhook_handlers_test.dartbackend/test/utils/pan_crypto_test.dart
…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>
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>
There was a problem hiding this comment.
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 winStale
mockExecutorverification no longer proves anything.This test intends to prove "no slot update occurs when no appointment is found," but it verifies
mockExecutor.executeMutationwas never called — a path thatconfirmSlotsno longer uses at all (it now callsmockSlots.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 winFilter tests no longer verify the filter is actually applied.
applies domain filter,applies search query, andapplies minimum rating filterall matchfindManyProjected/countwithwhere: any(named: 'where')and only assert on unrelated output (e.g.,priceCurrencydefault) or call counts. None of them capture/inspect the actualwherevalue, so a broken domain/search/rating filter translation would not be caught by these tests.Consider using
captureAny(named: 'where')(viaverify(...).captured) to assert the constructed filter actually contains the expecteddomainId/search term/ratingcondition.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
📒 Files selected for processing (11)
.github/workflows/backend-ci.yml.github/workflows/flutter-ci.ymlbackend/analysis_options.yamlbackend/lib/database/repositories/checkout_repository.dartbackend/scripts/regenerate-build.shbackend/test/helpers/prisma_mocks.dartbackend/test/repositories/appointment_repository_test.dartbackend/test/repositories/checkout_repository_test.dartbackend/test/repositories/consultant_explore_repository_test.dartbackend/test/routes/checkout/verify_test.dartbackend/test/services/webhook_handlers_test.dart
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.
Retire JsonQueryBuilder: full typed-delegate migration + web-schema re-sync
Migrates the entire backend data layer from string-based
JsonQueryBuilder(JQB) to the generated typedPrismaClientdelegates ofprisma_flutter_connector0.8.0, bundled with the schema re-sync from thefamiliarise_websource of truth. Typos in model/field names now fail at compile time instead of silently at runtime.Numbers
JsonQueryBuilder()sitesfindManyRaw/findFirstRawsitesdart analyzeerrorsA 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
slots→slotsOfAppointment,class→classRef,Invoice/Payoutremoval,WebinarCollaborator+ClassCollaborator→Collaborator(bps revenue share),supportTicketId→ticketId,freeTrial*→trial*,consultantShare→consultantSharePaise, droppedenrollmentStatus(filter semantically rescued), phantomtotalRevenuecolumns removed.db.prisma.$transactions.XRelationFilter(is_:)chains replacingFilterOperators.relationPath— SQL-equivalence-tested in the connector), typed include-with-select, projected finders (findManyProjected) for every.select()/.selectFields()/distinct/computed site, typedgroupBy/aggregate.registerAllModels()(hand-maintained builder retired); local-vs-hosted TLS derived from host.Intentional semantics notes (reviewed)
updateMany/deleteManypreserve that (typedupdate/deletethrow).id/createdAt/updatedAtwrites removed — the connector autofills them.slots,freeTrialDurationMinutes,requestStatus… keys unchanged even where source columns renamed).Verification (live, not guesswork)
relationPathsites) →POST /api/appointments201 (typed transaction +createManyAndReturn+ exempt junction insert) → list/detail/reschedule/cancel 200 → dashboard stats reflect the booking.String[]on 2 mock rows) was repaired; codegen tolerance queued for connector 0.9.0.QueryExecutor). New sharedtest/helpers/prisma_mocks.dartprovides delegate mocks, typed-input fallbacks, model builders, and a$transactionstub.Defects the repaired tests caught (all fixed here)
updatemethods threw instead of returning null (typedupdate=findUniqueOrThrow)null -> 404contract;PUT /api/user/[id]would have 500'dvalidateDiscountCodecastmaxDiscountas num?— it's a BigInt column thattoJson()emits as a StringTypeErroron every discount code with a cap setsupport_ticket.createTicket,checkout.updatePaymentStatus/updateBookingStatus,trial.updateStatus,collaborator.respondToCollaborationissueType: 'PAYMENT',PaymentStatus: 'COMPLETED') — they only passed because the executor mock swallowed themConnector releases shipped in tandem
ScalarFieldenums, include-with-select, projected finders) + 5 runtime@map/relation fixes found by live testing.Since the PR opened (all landed on this branch)
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), exploreuserOrgIdsorg-plan visibility, BigInt-robust earnings parsing, strict ISO-4217 currency validation; took dev's invoices 501 stub; converted dev's neworganization_repositoryraw calls to typed. Dev's/api/me/organization+/api/me/program-assignmentsverified 200 through the typed layer.setNullon updates,isNullfilters, nested M2Mset, null-tolerant array decode, raw helpers removed) → the last 4 exempt sites converted; JQB is now literally 0 (setNullverified live).DatabaseClientswitched toPostgresAdapter.pooled(8 conns, 30-min recycle) — fixes the recurring stale-single-connection 500s observed twice during verification.🤖 Generated with Claude Code
Summary by CodeRabbit