feat(dev): Docker Compose local stack + hash-guarded codegen - #123
feat(dev): Docker Compose local stack + hash-guarded codegen#123teetangh wants to merge 4 commits into
Conversation
The backend dev loop was slow for two independent reasons, and only one of them was the database. Codegen dominated. backend/lib/generated/ is ~735k lines across 498 files and is gitignored, and regenerate-build.sh deleted it unconditionally on every run — discarding build_runner's asset graph and forcing a cold rebuild even when nothing had changed. There was also no build.yaml, so json_serializable ran as a build phase over the whole package while producing zero output (the Prisma connector writes fromJson/toJson by hand; the one .g.dart comes from the Prisma generator, not build_runner). The database was remote. backend/.env pointed at Supabase's Mumbai pooler at ~40ms RTT; appointment_repository.dart alone issues 121 queries. Adds: - docker-compose.yml — Postgres, Dart Frog (hot reload), Flutter web. `docker compose up` for everything, `docker compose up db-init` for the DB-only fast path. Every heavy directory (.dart_tool, lib/generated, .dart_frog, build, pub cache) is a named volume, so it stays off macOS virtiofs. For .dart_tool that is correctness, not tuning: package_config.json holds absolute host paths that do not exist in the container. - docker/db-init — prisma db push from the in-repo schema, then the ledger triggers and CHECK constraints vendored from familiarise_web (db push does not manage those), then a seed harvested from prompts/testing/unit/*.md. Refuses to provision any host that is not local, so a mistyped URL cannot run --accept-data-loss against production. - backend/build.yaml — disables json_serializable, which also drops source_gen's combining_builder and part_cleanup and freezed's runs_before barrier. - backend/scripts/ensure-generated.sh — regenerates only when schema.prisma, the connector/freezed versions or build.yaml actually change. - scripts/use-db.sh, scripts/dev-backend.sh, Makefile, docs. backend/main.dart now resolves config as .env -> .env.local -> process environment, last winning. DotEnv's own includePlatformEnvironment injects the environment in the constructor and then lets the file overwrite it, which is backwards for containers: a stale DIRECT_URL in a bind-mounted .env would silently hijack a "local" run. Startup now also logs the resolved database host (never credentials) so that is visible rather than silent. Dockerfile, .dockerignore and the health route are carried over from PR #111, whose header comments document five deployment failures — notably that the base image must be Flutter, not dart:stable, because prisma_flutter_connector declares a Flutter SDK dependency. Its main.dart and schema.prisma changes are deliberately not taken; schema.prisma is owned by PR #122. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Concatenating the 23 `## Data Seeding` blocks into a single transaction does not work, for two reasons found by actually running it. Domain.name is UNIQUE and 17 prompts insert a domain named 'Technology' with different ids. Their `ON CONFLICT (id) DO NOTHING` does not cover a name collision, and the rows cannot just be dropped because each block's ConsultantProfile references its own domainId. The generator now suffixes the name with the row id, keeping foreign keys intact and names unique. Four prompts have also drifted from the schema — they reference `support_tickets` (the model is SupportTicket), `WebinarCollaborator`, and a `ConsultantProfile.totalRevenue` column that no longer exists. In one transaction the first of those aborted the entire seed. So emit one file per prompt into backend/prisma/sql/seed.d/ and apply each in its own transaction, reporting the ones that fail by name. Drift now costs one fixture set and is visible in the log, rather than silently taking out everything after it. Currently 19 of 23 blocks apply. Verified from a clean volume: db push syncs in ~600ms, both sidecars apply, 127 tables / 100 enums, ledger_txn_balanced and the CHECK constraints present, 28 seeded users, db-init exits 0. A second run is a no-op. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…on recovery Hit while verifying: the host filled up mid-build, which made writes fail inside Docker's VM and corrupted its containerd content store. The failure mode is opaque (input/output error on cache-key computation, then on the metadata DB) and unfixable by 'docker system prune', because pruning must read the blobs that are unreadable. Document the symptom, the ~15GB host requirement, and the factory-reset recovery. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both only reproduce in Docker, which is why they survived the native run.
1. `Platform` was resolving to the wrong type. lib/generated/index.dart
exports a Platform enum generated from the Prisma schema, and
database_client.dart re-exports it, so the unprefixed `Platform` in
main.dart bound to that enum instead of dart:io's:
main.dart:33:23: Error: Member not found: 'environment'.
This is the gotcha already documented in database_client.dart's header.
Import dart:io's Platform under an `io` prefix.
2. ensure-generated.sh could not wipe lib/generated. Under docker-compose it
is a named-volume mount point, and unlinking a mount point fails with
"Device or resource busy". Clear the contents instead, which works both in
the container and natively.
Verified in Docker end to end: codegen 67s in-container, dart_frog dev serving,
startup logs "host=db:5432, db=familiarise" (not Supabase), /api/health 200 in
3ms, /api/consultants and /api/domains return seeded rows through the typed
delegates, /api/announcements correctly 401s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
📝 WalkthroughWalkthroughThe PR adds Docker-first local development for Postgres, the Dart Frog API, and Flutter web, with environment precedence, guarded schema provisioning, database integrity SQL, generated test seeds, code-generation caching, health checks, helper commands, and updated setup documentation. ChangesLocal development stack
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DockerCompose
participant Postgres
participant DbInit
participant Api
participant Web
DockerCompose->>Postgres: Start and healthcheck database
DockerCompose->>DbInit: Start after database health
DbInit->>Postgres: Push schema, apply constraints, and seed fixtures
DockerCompose->>Api: Start API after initialization
Api->>Api: Run Dart Frog development server
DockerCompose->>Web: Start web service after API health
Web->>Api: Use configured API base URL
Possibly related issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 16
🤖 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/Dockerfile`:
- Around line 72-82: Run both API containers as non-root users. In
backend/Dockerfile lines 72-82, create an unprivileged runtime user, copy the
server with ownership assigned to that user, and switch to it before CMD. In
backend/Dockerfile.dev lines 16-43, create and switch to an unprivileged user
while preserving writable named-volume initialization.
In `@backend/prisma/sql/check-constraints.sql`:
- Around line 168-176: Update the tds_record_financial_year_format and
consultant_payout_tds_fy_format CHECK constraints so they validate that the
two-digit suffix represents the year immediately following the four-digit
prefix, rather than only matching the YYYY-YY shape; preserve the nullable
behavior of ConsultantPayout.tdsFinancialYear.
In `@backend/prisma/sql/seed.d/01-auth.sql`:
- Around line 9-11: Replace the shared password hash source in scripts/use-db.sh
with a valid 60-character bcrypt.dart hash, then regenerate
backend/prisma/sql/seed.d/ so the credential account fixtures use it
consistently. Apply this across backend/prisma/sql/seed.d/01-auth.sql (lines
9-11), 02-onboarding.sql (12-15), 03-profile.sql (10-12), 04-verification.sql
(10-12), 05-plans.sql (10-12), 06-slots.sql (10-12), 15-reviews.sql (12-15),
16-support.sql (10-12), and 17-feedback.sql (9-11); do not edit generated SQL
files directly.
In `@backend/prisma/sql/seed.d/02-onboarding.sql`:
- Around line 9-10: Update the source prompt for the test_unit_onb_cnt
consultant fixture to use the CONSULTANT role instead of CONSULTEE, then
regenerate the onboarding seed SQL so the generated insert matches the corrected
role.
In `@backend/prisma/sql/seed.d/07-explore.sql`:
- Around line 12-15: Replace the malformed shared bcrypt hash in credential seed
rows with one valid bcrypt hash for the documented development password,
preserving the existing account data and password semantics. Apply this update
in backend/prisma/sql/seed.d/07-explore.sql lines 12-15, 08-booking.sql lines
12-15, 09-checkout.sql lines 12-15, 10-trials.sql lines 12-15, 11-waitlist.sql
lines 13-16, 12-documents.sql lines 12-15, 13-chat.sql lines 10-12, and
14-referrals.sql lines 10-12.
In `@backend/prisma/sql/seed.d/11-waitlist.sql`:
- Around line 29-35: Update the waitlist seed data following the Webinar insert
for test_unit_waitlist_w1 to add the existing enrollment/attendee row for
test_unit_waitlist_u3, linked to that webinar and its expected user/registration
identifiers. Regenerate the seed so capacity checks observe the webinar as full
before exercising the waitlist path.
In `@backend/scripts/ensure-generated.sh`:
- Around line 44-62: Update the dependency setup and stamp validation around the
WANT calculation in ensure-generated.sh to account for pubspec.yaml and
pubspec.lock changes, including the .dart_tool/package_config.json state. Run
flutter pub get before calculating or accepting the codegen stamp, and ensure
unchanged codegen inputs cannot bypass dependency resolution when the manifest
or lockfile has changed.
In `@docker-compose.yml`:
- Line 67: Update the db-init service environment so its DIRECT_URL always uses
the local database connection and is not overridden by FAM_DIRECT_URL. Move the
FAM_DIRECT_URL override to the api service environment, preserving the local
fallback there.
In `@docker/db-init/entrypoint.sh`:
- Around line 75-82: Update the psql invocation in the seed-file loop to include
the --single-transaction option, ensuring each prisma/sql/seed.d/*.sql file runs
atomically and rolls back completely on failure. Preserve the existing error
capture, success counting, and skipped-file reporting behavior.
In `@docker/web/Dockerfile`:
- Line 13: Update the Dockerfile’s Flutter base image reference from the mutable
stable tag to a tested, explicit Flutter release pinned by its immutable image
digest. Preserve the existing Flutter image source while ensuring future
rebuilds always use the same Flutter/Dart toolchain.
In `@docker/web/entrypoint.sh`:
- Around line 18-36: Update the .env generation guard in the entrypoint script
to construct the complete expected configuration, compare it against the
existing .env contents, and atomically replace .env whenever any generated value
differs. Ensure changes to all settings, not just API_BASE_URL, trigger
regeneration while preserving the existing generated values and file format.
In `@docs/getting-started/05-docker-local-dev.md`:
- Around line 87-93: Remove the Supabase-targeted Docker Compose command from
the getting-started documentation and state that Supabase is supported only with
the native backend workflow using scripts/use-db.sh supabase. Do not document a
remote database Compose path unless the db-init behavior is explicitly made safe
for non-local hosts.
- Around line 135-138: Update the Docker local development seed documentation to
explicitly describe the seed state as incomplete when prompt seed files fail,
including that four of 23 fixtures are currently unavailable and may affect
dependent tests or seeded endpoints. Do not characterize these failures solely
as stale prompts or non-provisioning issues; either identify the missing
fixtures or document that the failures are fatal.
- Around line 110-115: Reconcile the schema count in the provisioning
documentation with the 129-table value reported by docker/db-init/entrypoint.sh,
using a shared source of truth if practical; otherwise remove the volatile table
count while retaining the prisma db push instructions.
In `@scripts/dev-backend.sh`:
- Around line 22-36: Update the load_env calls in the dev-backend
environment-loading flow to load .env.local before .env, allowing local values
to remain effective when the later file only sets absent variables. Adjust the
presence check in load_env so an explicitly inherited empty variable is treated
as already set and is not overwritten.
In `@scripts/gen-dev-seed.sh`:
- Around line 19-21: Update the gen-dev-seed.sh flow around OUT_DIR so
generation occurs in a temporary sibling staging directory rather than deleting
seed.d upfront. Run the complete generation and validation loop against the
staging directory, and only after success atomically replace OUT_DIR with the
validated staging contents; preserve the existing fixture output when any
command fails.
🪄 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: a3b836c0-e5c2-4607-b884-049f370a4854
⛔ Files ignored due to path filters (1)
backend/pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (48)
Makefilebackend/.dockerignorebackend/.env.examplebackend/.env.supabase.examplebackend/.gitignorebackend/Dockerfilebackend/Dockerfile.devbackend/build.yamlbackend/docker/dev-entrypoint.shbackend/main.dartbackend/prisma/sql/check-constraints.sqlbackend/prisma/sql/ledger-triggers.sqlbackend/prisma/sql/seed.d/01-auth.sqlbackend/prisma/sql/seed.d/02-onboarding.sqlbackend/prisma/sql/seed.d/03-profile.sqlbackend/prisma/sql/seed.d/04-verification.sqlbackend/prisma/sql/seed.d/05-plans.sqlbackend/prisma/sql/seed.d/06-slots.sqlbackend/prisma/sql/seed.d/07-explore.sqlbackend/prisma/sql/seed.d/08-booking.sqlbackend/prisma/sql/seed.d/09-checkout.sqlbackend/prisma/sql/seed.d/10-trials.sqlbackend/prisma/sql/seed.d/11-waitlist.sqlbackend/prisma/sql/seed.d/12-documents.sqlbackend/prisma/sql/seed.d/13-chat.sqlbackend/prisma/sql/seed.d/14-referrals.sqlbackend/prisma/sql/seed.d/15-reviews.sqlbackend/prisma/sql/seed.d/16-support.sqlbackend/prisma/sql/seed.d/17-feedback.sqlbackend/prisma/sql/seed.d/18-payout.sqlbackend/prisma/sql/seed.d/19-tax.sqlbackend/prisma/sql/seed.d/20-staff.sqlbackend/prisma/sql/seed.d/21-announcements.sqlbackend/prisma/sql/seed.d/22-collaborations.sqlbackend/prisma/sql/seed.d/23-dashboard.sqlbackend/routes/api/health.dartbackend/scripts/ensure-generated.shdocker-compose.ymldocker/db-init/Dockerfiledocker/db-init/entrypoint.shdocker/web/Dockerfiledocker/web/entrypoint.shdocs/getting-started/04-local-testing-guide.mddocs/getting-started/05-docker-local-dev.mdscripts/dev-backend.shscripts/gen-dev-seed.shscripts/regenerate-build.shscripts/use-db.sh
| FROM debian:bookworm-slim | ||
|
|
||
| RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
| ca-certificates \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| COPY --from=build /app/build/bin/server /app/bin/server | ||
|
|
||
| EXPOSE 8080 | ||
|
|
||
| CMD ["/app/bin/server"] |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
Run both API containers as a non-root user. Both images retain the default root user, so a compromise of the Dart Frog process has root privileges inside its container.
backend/Dockerfile#L72-L82: create an unprivileged runtime user, copy the server with appropriate ownership, and switch to it beforeCMD.backend/Dockerfile.dev#L16-L43: create and switch to an unprivileged user while ensuring named-volume initialization remains writable.
🧰 Tools
🪛 Checkov (3.3.8)
[low] 1-82: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
[low] 1-82: Ensure that a user for the container has been created
(CKV_DOCKER_3)
🪛 Hadolint (2.14.0)
[warning] 74-74: Pin versions in apt get install. Instead of apt-get install <package> use apt-get install <package>=<version>
(DL3008)
📍 Affects 2 files
backend/Dockerfile#L72-L82(this comment)backend/Dockerfile.dev#L16-L43
🤖 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/Dockerfile` around lines 72 - 82, Run both API containers as non-root
users. In backend/Dockerfile lines 72-82, create an unprivileged runtime user,
copy the server with ownership assigned to that user, and switch to it before
CMD. In backend/Dockerfile.dev lines 16-43, create and switch to an unprivileged
user while preserving writable named-volume initialization.
Source: Linters/SAST tools
| ALTER TABLE "TDSRecord" DROP CONSTRAINT IF EXISTS "tds_record_financial_year_format"; | ||
| -- SPLIT | ||
| ALTER TABLE "TDSRecord" ADD CONSTRAINT "tds_record_financial_year_format" | ||
| CHECK ("financialYear" ~ '^[0-9]{4}-[0-9]{2}$'); | ||
| -- SPLIT | ||
| ALTER TABLE "ConsultantPayout" DROP CONSTRAINT IF EXISTS "consultant_payout_tds_fy_format"; | ||
| -- SPLIT | ||
| ALTER TABLE "ConsultantPayout" ADD CONSTRAINT "consultant_payout_tds_fy_format" | ||
| CHECK ("tdsFinancialYear" IS NULL OR "tdsFinancialYear" ~ '^[0-9]{4}-[0-9]{2}$'); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Validate the financial-year relationship, not only its shape.
2026-99 passes both regexes even though the suffix must be the following year (2026-27). Enforce that relationship for both columns.
Proposed fix
- CHECK ("financialYear" ~ '^[0-9]{4}-[0-9]{2}$');
+ CHECK (
+ "financialYear" ~ '^[0-9]{4}-[0-9]{2}$'
+ AND substring("financialYear" FROM 6 FOR 2)::integer =
+ (substring("financialYear" FROM 1 FOR 4)::integer + 1) % 100
+ );🤖 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/prisma/sql/check-constraints.sql` around lines 168 - 176, Update the
tds_record_financial_year_format and consultant_payout_tds_fy_format CHECK
constraints so they validate that the two-digit suffix represents the year
immediately following the four-digit prefix, rather than only matching the
YYYY-YY shape; preserve the nullable behavior of
ConsultantPayout.tdsFinancialYear.
| INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") | ||
| VALUES ('test_unit_auth_a1', 'test_unit_auth_u1', 'test_unit_auth_u1', 'credential', | ||
| '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== repository files containing seed.d =="
git ls-files | grep -E 'backend/prisma/sql/seed\.d/|Seed|seeding|prisma' | head -200
echo
echo "== target SQL snippets =="
for f in \
backend/prisma/sql/seed.d/01-auth.sql \
backend/prisma/sql/seed.d/02-onboarding.sql \
backend/prisma/sql/seed.d/03-profile.sql \
backend/prisma/sql/seed.d/04-verification.sql \
backend/prisma/sql/seed.d/05-plans.sql \
backend/prisma/sql/seed.d/06-slots.sql \
backend/prisma/sql/seed.d/15-reviews.sql \
backend/prisma/sql/seed.d/16-support.sql \
backend/prisma/sql/seed.d/17-feedback.sql
do
if [ -f "$f" ]; then
echo "--- $f"
nl -ba "$f" | sed -n '1,80p'
else
echo "MISSING $f"
fi
done
echo
echo "== source prompt occurrences of hash or Data Seeding =="
rg -n '"Data Seeding"|password|test_unit_auth_a1|test_unit_onb_a1|test_unit_profile_a1|test_unit_verif_a1|test_unit_plans_a1|test_unit_slots_a1|test_unit_reviews|test_unit_support_a1|test_unit_feedback|Data Seeding' -S . --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' | head -300Repository: Practitionist/familiarise_mobile
Length of output: 1385
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== target SQL snippets =="
for f in \
backend/prisma/sql/seed.d/01-auth.sql \
backend/prisma/sql/seed.d/02-onboarding.sql \
backend/prisma/sql/seed.d/03-profile.sql \
backend/prisma/sql/seed.d/04-verification.sql \
backend/prisma/sql/seed.d/05-plans.sql \
backend/prisma/sql/seed.d/06-slots.sql \
backend/prisma/sql/seed.d/15-reviews.sql \
backend/prisma/sql/seed.d/16-support.sql \
backend/prisma/sql/seed.d/17-feedback.sql
do
if [ -f "$f" ]; then
echo "--- $f"
awk '{printf "%6d\t%s\n", NR, $0}' "$f" | sed -n '1,80p'
else
echo "MISSING $f"
fi
done
echo
echo "== all seed account inserts and hash occurrences =="
python3 - <<'PY'
import pathlib, re
for p in sorted(pathlib.Path('backend/prisma/sql/seed.d').glob('*.sql')):
text = p.read_text()
for m in re.finditer(r'VALUES\s*\([^;]+accounts', text, re.S):
snippet = text[m.start():m.end()]
print(f'--- {p} @ line {text[:m.start()].count(chr(10))+1}')
line = text[:m.start()].count('\n') + 1
for j, line in enumerate(snippet.splitlines(), start=line):
if 'test_unit_' in line or 'credential' in line or 'password' in line:
print(f'{j}: {line}')
PY
echo
echo "== repository text occurrences for source prompt/seed generator hints =="
rg -n 'Data Seeding|seed\.d|seed generator|prisma/sql/seed\.d|credential|passwordHash|verify|bcrypt' -S . \
--glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' | head -300Repository: Practitionist/familiarise_mobile
Length of output: 50390
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
import subprocess, re, pathlib
pwd = subprocess.check_output(['pwd'], text=True).strip()
print("PWD:", pwd)
for p in range(95, 66):
try:
subprocess.check_output(['python'+str(p % 10 if p>=10 else p), '--version'], stderr=subprocess.PIPE)
except:
pass
try:
import bcrypt
print("bcrypt module available:", bcrypt.__version__)
except Exception as e:
print("bcrypt not available:", type(e).__name__, e)
print("imports of cryptography available:", bool(importlib.util.find_spec("cryptography") if __import__("importlib").util else False))
PYRepository: Practitionist/familiarise_mobile
Length of output: 584
🌐 Web query:
bcrypt Dart package BCrypt.checkpw hash format expected length checkpw returns false hash too short
💡 Result:
In the Dart bcrypt package, the BCrypt.checkpw function returns false if the provided hash is malformed, truncated, or otherwise does not conform to the standard bcrypt format [1][2]. Because bcrypt is a deterministic hashing algorithm, any change to the stored hash—including truncation—renders it invalid, causing the verification check to fail [3][4][5]. A standard bcrypt hash is expected to be 60 characters long [3][4][6]. If your database column storing the hash is too short (e.g., a VARCHAR(50) field), the hash will be truncated upon insertion, making it impossible to successfully verify against the original password later [3][4][5]. To resolve this issue, ensure your storage schema supports a minimum length of 60 characters (often recommended as TEXT or VARCHAR(60+)) [3][4][5]. Additionally, verify the following: 1. Hash Format: The hash string must follow the structure:
Citations:
- 1: https://pub.dev/documentation/bcrypt/latest/bcrypt/BCrypt-class.html
- 2: https://pub.dev/documentation/bcrypt/latest/index.html
- 3: https://stackoverflow.com/questions/51008351/bcrypt-hashedsecret-too-short-to-be-a-bcrypted-password
- 4: The compare method's result is false if hashed password is stored/retrieved from database kelektiv/node.bcrypt.js#466
- 5: bcrypt.compare always returns false kelektiv/node.bcrypt.js#906
- 6: https://github.com/ncb000gt/node.bcrypt.js/blob/master/README.md
Use valid 60-character bcrypt hashes in the seed fixtures.
The generated account rows in these seed files store a 58-character password instead of a valid bcrypt hash; BCrypt.checkpw rejects it, so these credential-authenticated test accounts cannot sign in. Update the shared source value in scripts/use-db.sh to a valid bcrypt.dart hash, then regenerate backend/prisma/sql/seed.d/; avoid editing the generated SQL files directly.
📍 Affects 9 files
backend/prisma/sql/seed.d/01-auth.sql#L9-L11(this comment)backend/prisma/sql/seed.d/02-onboarding.sql#L12-L15backend/prisma/sql/seed.d/03-profile.sql#L10-L12backend/prisma/sql/seed.d/04-verification.sql#L10-L12backend/prisma/sql/seed.d/05-plans.sql#L10-L12backend/prisma/sql/seed.d/06-slots.sql#L10-L12backend/prisma/sql/seed.d/15-reviews.sql#L12-L15backend/prisma/sql/seed.d/16-support.sql#L10-L12backend/prisma/sql/seed.d/17-feedback.sql#L9-L11
🤖 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/prisma/sql/seed.d/01-auth.sql` around lines 9 - 11, Replace the
shared password hash source in scripts/use-db.sh with a valid 60-character
bcrypt.dart hash, then regenerate backend/prisma/sql/seed.d/ so the credential
account fixtures use it consistently. Apply this across
backend/prisma/sql/seed.d/01-auth.sql (lines 9-11), 02-onboarding.sql (12-15),
03-profile.sql (10-12), 04-verification.sql (10-12), 05-plans.sql (10-12),
06-slots.sql (10-12), 15-reviews.sql (12-15), 16-support.sql (10-12), and
17-feedback.sql (9-11); do not edit generated SQL files directly.
| ('test_unit_onb_cee', 'Onb Consultee', 'test_unit_onb_cee@test.com', true, 'CONSULTEE', false, NOW(), NOW()), | ||
| ('test_unit_onb_cnt', 'Onb Consultant', 'test_unit_onb_cnt@test.com', true, 'CONSULTEE', false, NOW(), NOW()); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Assign the consultant fixture the CONSULTANT role.
test_unit_onb_cnt is labeled Onb Consultant but is inserted with role CONSULTEE. This causes consultant onboarding tests to run with the wrong role and permissions. Fix the source prompt and regenerate the seed SQL.
🤖 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/prisma/sql/seed.d/02-onboarding.sql` around lines 9 - 10, Update the
source prompt for the test_unit_onb_cnt consultant fixture to use the CONSULTANT
role instead of CONSULTEE, then regenerate the onboarding seed SQL so the
generated insert matches the corrected role.
| INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") | ||
| VALUES | ||
| ('test_unit_explore_a1', 'test_unit_explore_u1', 'test_unit_explore_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), | ||
| ('test_unit_explore_a2', 'test_unit_explore_u2', 'test_unit_explore_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/sh
python3 - <<'PY'
from pathlib import Path
import re
for path in Path("backend/prisma/sql/seed.d").glob("*.sql"):
for value in re.findall(r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", path.read_text()):
if len(value) != 60:
print(f"{path}: invalid bcrypt length {len(value)}: {value}")
PYRepository: Practitionist/familiarise_mobile
Length of output: 4388
🏁 Script executed:
#!/bin/sh
set -eu
echo "Candidate files:"
git ls-files backend/prisma schema.prisma | sed -n '1,120p'
echo
echo "Search password hashing/comparisons:"
rg -n "bcrypt|comparePassword|password|credentials|credential|passwordHash|setPassword|genSalt|hash" --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' .
echo
echo "Relevant auth files outline:"
for f in $(git ls-files | rg 'backend/.*/auth|backend/.*/credentials|credentials|auth' | head -50); do
if [ -f "$f" ]; then
echo "--- $ f"
ast-grep outline "$f" 2>/dev/null | sed -n '1,80p' || true
fi
doneRepository: Practitionist/familiarise_mobile
Length of output: 50389
🏁 Script executed:
#!/bin/sh
set -eu
echo "Backend auth service:"
sed -n '1,180p' backend/lib/services/auth/auth_service.dart
echo
echo "Backend profile password code (current/current-password path):"
sed -n '50,75p' backend/lib/services/profile/profile_service.dart
echo
echo "Seed rows with malformed hash count:"
python3 - <<'PY'
from pathlib import Path
import re
for p in sorted(Path("backend/prisma/sql/seed.d").glob("*.sql")):
text = p.read_text()
bad = re.findall(regex := r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", text)
bad = [h for h in bad if len(h) != 60]
if bad:
print(f"{p}: {len(bad)} malformed credential hash values")
PY
echo
echo "Markdown seed copy count:"
python3 - <<'PY'
from pathlib import Path, PurePosixPath
import re
patterns = ("backend/testing/unit", "prompts/testing/unit")
count = 0
for root in patterns:
for p in Path(root).glob("backends/seed.d/*.sql"):
bad = re.findall(r"\$2[aby]\$\d{2}\$[./A-Za-z0-9]+", p.read_text())
bad = [h for h in bad if len(h) != 60]
count += len(bad)
print("markdown seed copies:", count)
PYRepository: Practitionist/familiarise_mobile
Length of output: 8382
Replace the malformed shared bcrypt hashes in credential seed fixtures.
$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z is 58 characters, but the credential sign-in path uses BCrypt.checkpw, so seeded accounts that use these hashes sign in as invalid credentials. Generate a valid bcrypt hash for the documented dev password and update the affected backend/prisma/sql/seed.d/*.sql credential rows.
📍 Affects 8 files
backend/prisma/sql/seed.d/07-explore.sql#L12-L15(this comment)backend/prisma/sql/seed.d/08-booking.sql#L12-L15backend/prisma/sql/seed.d/09-checkout.sql#L12-L15backend/prisma/sql/seed.d/10-trials.sql#L12-L15backend/prisma/sql/seed.d/11-waitlist.sql#L13-L16backend/prisma/sql/seed.d/12-documents.sql#L12-L15backend/prisma/sql/seed.d/13-chat.sql#L10-L12backend/prisma/sql/seed.d/14-referrals.sql#L10-L12
🤖 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/prisma/sql/seed.d/07-explore.sql` around lines 12 - 15, Replace the
malformed shared bcrypt hash in credential seed rows with one valid bcrypt hash
for the documented development password, preserving the existing account data
and password semantics. Apply this update in
backend/prisma/sql/seed.d/07-explore.sql lines 12-15, 08-booking.sql lines
12-15, 09-checkout.sql lines 12-15, 10-trials.sql lines 12-15, 11-waitlist.sql
lines 13-16, 12-documents.sql lines 12-15, 13-chat.sql lines 10-12, and
14-referrals.sql lines 10-12.
| # Containers, pointed at Supabase instead: | ||
| FAM_DIRECT_URL='postgresql://…@aws-0-….pooler.supabase.com:5432/postgres' docker compose up | ||
|
|
||
| # Native backend: | ||
| source scripts/use-db.sh local | ||
| source scripts/use-db.sh supabase # needs backend/.env.supabase | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Remove or correct the unsupported Supabase Docker workflow.
FAM_DIRECT_URL=... docker compose up sends the remote URL to db-init, but docker/db-init/entrypoint.sh intentionally exits for every host except db, postgres, localhost, and 127.0.0.1. This documented command therefore cannot start the full stack as written. Document Supabase as native-backend-only, or add a safe Compose path that bypasses destructive provisioning for remote databases.
🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 87 - 93, Remove the
Supabase-targeted Docker Compose command from the getting-started documentation
and state that Supabase is supported only with the native backend workflow using
scripts/use-db.sh supabase. Do not document a remote database Compose path
unless the db-init behavior is explicitly made safe for non-local hosts.
| There is no `migrations/` directory in this repo — `familiarise_web` owns | ||
| migrations. Provisioning therefore runs, in order: | ||
|
|
||
| 1. `prisma db push` from the in-repo `backend/prisma/schema.prisma`, using | ||
| Prisma 7.7.0 to match `familiarise_web`. Takes well under a second and | ||
| produces 127 tables and 100 enums. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Reconcile the documented table count.
docker/db-init/entrypoint.sh states that provisioning creates 129 tables, while this guide says 127. Use one source-of-truth count, or omit the volatile count to avoid misleading schema-drift diagnosis.
🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 110 - 115,
Reconcile the schema count in the provisioning documentation with the 129-table
value reported by docker/db-init/entrypoint.sh, using a shared source of truth
if practical; otherwise remove the volatile table count while retaining the
prisma db push instructions.
| Those are **stale prompts, not provisioning failures** — the model is | ||
| `SupportTicket`, not `support_tickets`. Fixing the prompts is worthwhile but out | ||
| of scope here; running them per-block means the drift is visible and costs one | ||
| fixture set instead of the entire seed. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Describe the seed state as incomplete.
Four of 23 prompt seed files currently fail, so the database is only partially seeded. Calling these “not provisioning failures” may mislead developers whose tests or seeded endpoints depend on those fixtures; identify the unavailable fixtures or make the failures fatal.
🤖 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 `@docs/getting-started/05-docker-local-dev.md` around lines 135 - 138, Update
the Docker local development seed documentation to explicitly describe the seed
state as incomplete when prompt seed files fail, including that four of 23
fixtures are currently unavailable and may affect dependent tests or seeded
endpoints. Do not characterize these failures solely as stale prompts or
non-provisioning issues; either identify the missing fixtures or document that
the failures are fatal.
| # Export .env, then .env.local, without clobbering anything already exported — | ||
| # an explicit `source scripts/use-db.sh` must still win. | ||
| load_env() { | ||
| [ -f "$1" ] || return 0 | ||
| while IFS= read -r line; do | ||
| case "$line" in ''|'#'*) continue ;; esac | ||
| key=${line%%=*} | ||
| key=${key// /} | ||
| [ -n "${!key:-}" ] && continue # already exported: leave it alone | ||
| export "${key?}=${line#*=}" | ||
| done < "$1" | ||
| } | ||
|
|
||
| load_env .env | ||
| load_env .env.local |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Restore .env.local override precedence.
Loading .env first exports its keys, so line 30 skips the corresponding .env.local values. A developer’s local DIRECT_URL therefore cannot override a shared hosted URL. Reverse the load order under this “only set if absent” implementation, and preserve explicitly empty inherited environment variables as overrides too.
🤖 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 `@scripts/dev-backend.sh` around lines 22 - 36, Update the load_env calls in
the dev-backend environment-loading flow to load .env.local before .env,
allowing local values to remain effective when the later file only sets absent
variables. Adjust the presence check in load_env so an explicitly inherited
empty variable is treated as already set and is not overwritten.
| OUT_DIR=backend/prisma/sql/seed.d | ||
| rm -rf "$OUT_DIR" | ||
| mkdir -p "$OUT_DIR" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Generate into a staging directory before replacing fixtures.
An awk, grep, or Python failure after Line 20 exits under set -e and leaves backend/prisma/sql/seed.d empty or partial. Build and validate in a temporary sibling directory, then atomically replace OUT_DIR only after the full loop succeeds.
🤖 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 `@scripts/gen-dev-seed.sh` around lines 19 - 21, Update the gen-dev-seed.sh
flow around OUT_DIR so generation occurs in a temporary sibling staging
directory rather than deleting seed.d upfront. Run the complete generation and
validation loop against the staging directory, and only after success atomically
replace OUT_DIR with the validated staging contents; preserve the existing
fixture output when any command fails.
Why
The backend dev loop is slow for two independent reasons, and only one of them is the database.
Codegen dominated.
backend/lib/generated/is ~735k lines across 498 files and is gitignored, andregenerate-build.shdeleted it unconditionally on every run — discarding build_runner's asset graph and forcing a cold rebuild even when nothing had changed. There was also nobuild.yaml, sojson_serializableran as a build phase over the whole package while producing zero output (the Prisma connector hand-writesfromJson/toJson; the one.g.dartcomes from the Prisma generator, not build_runner).The database was remote.
backend/.envpoints at Supabase's Mumbai pooler. Measured on a warm pooled connection: 57.03 ms per query vs 0.23 ms locally — 248×.appointment_repository.dartalone issues 121 queries.What
docker-compose.yml— Postgres, Dart Frog (hot reload), Flutter web.docker compose upfor everything;docker compose up db-initfor the DB-only fast path. Every heavy directory (.dart_tool,lib/generated,.dart_frog,build, pub cache) is a named volume so it stays off macOS virtiofs. For.dart_toolthat is correctness, not tuning:package_config.jsonholds absolute host paths that don't exist in the container.docker/db-init—prisma db pushfrom the in-repo schema, then the ledger triggers and CHECK constraints vendored fromfamiliarise_web(db pushdoesn't manage those), then a seed extracted fromprompts/testing/unit/*.md. Refuses to provision any non-local host, so a mistyped URL can't run--accept-data-lossagainst production.backend/build.yaml— disablesjson_serializable, which also dropssource_gen'scombining_builderandpart_cleanupand freezed'sruns_beforebarrier.backend/scripts/ensure-generated.sh— regenerates only whenschema.prisma, the connector/freezed versions, orbuild.yamlactually change.scripts/use-db.sh,scripts/dev-backend.sh,Makefile, docs.main.dartnow resolves config as.env→.env.local→ process environment, last winning. DotEnv's ownincludePlatformEnvironmentinjects the environment in the constructor and then lets the file overwrite it, which is backwards for containers: a staleDIRECT_URLin a bind-mounted.envwould silently hijack a "local" run. Startup also logs the resolved DB host (never credentials) so that's visible rather than silent.Dockerfile,.dockerignoreand the health route are carried over from #111, whose comments document five deployment failures — notably that the base image must be Flutter, notdart:stable. Itsmain.dartandschema.prismachanges are deliberately not taken;schema.prismais owned by #122.Verified end-to-end in Docker
prisma db pushledger_txn_balanced+ CHECK constraints present/api/health/api/consultants,/api/domainshost=db:5432, db=familiariseRunning it in Docker caught two bugs that cannot reproduce natively, both fixed here:
Platform.environmentbound to the Prisma-generatedPlatformenum re-exported bydatabase_client.dartinstead ofdart:io's (the gotcha that file's own header warns about), andrm -rf lib/generatedfailing with "Device or resource busy" because it's a volume mount point.Notes for review
main.dart.dev. Its Shorebird/Railway work is untouched.support_tickets(the model isSupportTicket),WebinarCollaborator, and a removedConsultantProfile.totalRevenue. Reported by name rather than silently swallowed; fixing the prompts is a follow-up.backend/pubspec.lockis un-ignored (the root one was always tracked) for reproducible image builds..github/workflows/flutter-ci.ymltriggers on[main, develop], but the default branch isdev— so CI has likely never run on a PR. There is also no backend job at all.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Developer Experience