From 40fe88e3b77c646403c5cec055b93ae701fb64ed Mon Sep 17 00:00:00 2001 From: Kaustav Ghosh Date: Sat, 25 Jul 2026 00:08:06 +0530 Subject: [PATCH 1/4] feat(dev): docker compose local stack + hash-guarded codegen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- Makefile | 60 ++ backend/.dockerignore | 11 + backend/.env.example | 32 +- backend/.env.supabase.example | 25 + backend/.gitignore | 13 +- backend/Dockerfile | 82 +++ backend/Dockerfile.dev | 43 ++ backend/build.yaml | 40 + backend/docker/dev-entrypoint.sh | 25 + backend/main.dart | 50 +- backend/prisma/sql/check-constraints.sql | 186 +++++ backend/prisma/sql/ledger-triggers.sql | 56 ++ backend/prisma/sql/seed-dev.sql | 689 ++++++++++++++++++ backend/routes/api/health.dart | 19 + backend/scripts/ensure-generated.sh | 75 ++ docker-compose.yml | 160 ++++ docker/db-init/Dockerfile | 39 + docker/db-init/entrypoint.sh | 73 ++ docker/web/Dockerfile | 31 + docker/web/entrypoint.sh | 49 ++ .../getting-started/04-local-testing-guide.md | 21 +- docs/getting-started/05-docker-local-dev.md | 177 +++++ scripts/dev-backend.sh | 46 ++ scripts/gen-dev-seed.sh | 47 ++ scripts/regenerate-build.sh | 66 +- scripts/use-db.sh | 70 ++ 26 files changed, 2135 insertions(+), 50 deletions(-) create mode 100644 Makefile create mode 100644 backend/.dockerignore create mode 100644 backend/.env.supabase.example create mode 100644 backend/Dockerfile create mode 100644 backend/Dockerfile.dev create mode 100644 backend/build.yaml create mode 100755 backend/docker/dev-entrypoint.sh create mode 100644 backend/prisma/sql/check-constraints.sql create mode 100644 backend/prisma/sql/ledger-triggers.sql create mode 100644 backend/prisma/sql/seed-dev.sql create mode 100644 backend/routes/api/health.dart create mode 100755 backend/scripts/ensure-generated.sh create mode 100644 docker-compose.yml create mode 100644 docker/db-init/Dockerfile create mode 100755 docker/db-init/entrypoint.sh create mode 100644 docker/web/Dockerfile create mode 100755 docker/web/entrypoint.sh create mode 100644 docs/getting-started/05-docker-local-dev.md create mode 100755 scripts/dev-backend.sh create mode 100755 scripts/gen-dev-seed.sh create mode 100755 scripts/use-db.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb68407 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +# Familiarise local development. +# +# make up everything (Postgres + API + Flutter web) +# make db Postgres only — the fast path if containers feel slow +# make down stop, keeping data and build caches +# +# `make help` lists everything. + +.DEFAULT_GOAL := help +.PHONY: help up db api down reset logs shell psql test regen regen-force seed-sql doctor + +help: ## Show this help + @grep -hE '^[a-z-]+:.*?## ' $(MAKEFILE_LIST) \ + | awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' + +up: ## Start the full stack (Postgres + API + Flutter web) + docker compose up + +db: ## Start Postgres only, provisioned — use with a native `dart_frog dev` + docker compose up db-init + +api: ## Start Postgres + API, no Flutter web + docker compose up db-init api + +down: ## Stop everything, KEEPING the database and build caches + docker compose down + +reset: ## Destroy the database and all build caches (next start is a cold rebuild) + docker compose down -v + +logs: ## Tail logs from all services + docker compose logs -f + +shell: ## Open a shell in the API container + docker compose exec api bash + +psql: ## Open psql against the local database + docker compose exec db psql -U familiarise -d familiarise + +test: ## Run the backend test suite inside the API container + docker compose exec api dart test + +regen: ## Backend codegen, skipped if schema/versions/build.yaml are unchanged + ./scripts/regenerate-build.sh --prisma + +regen-force: ## Backend codegen, wiping lib/generated first + ./scripts/regenerate-build.sh --prisma --force + +seed-sql: ## Rebuild backend/prisma/sql/seed-dev.sql from prompts/testing/unit/*.md + ./scripts/gen-dev-seed.sh + +doctor: ## Check that the local toolchain can run the stack + @printf 'docker : '; docker --version 2>/dev/null || echo 'MISSING' + @printf 'compose : '; docker compose version --short 2>/dev/null || echo 'MISSING' + @printf 'daemon : '; v=$$(docker info --format '{{.ServerVersion}} ({{.Architecture}})' 2>/dev/null); \ + [ -n "$$v" ] && [ "$$v" != " ()" ] && echo "$$v" || echo 'NOT RUNNING — start Docker Desktop' + @printf 'flutter : '; flutter --version 2>/dev/null | head -1 || echo 'MISSING' + @printf 'port 5433 : '; lsof -ti:5433 >/dev/null 2>&1 && echo 'IN USE — set FAM_PG_PORT' || echo 'free' + @printf 'port 8080 : '; lsof -ti:8080 >/dev/null 2>&1 && echo 'IN USE' || echo 'free' + @printf 'port 3000 : '; lsof -ti:3000 >/dev/null 2>&1 && echo 'IN USE' || echo 'free' diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..84c08df --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,11 @@ +.env +.env.* +!.env.example +.dart_tool/ +.packages +build/ +.git/ +.vscode/ +.serena/ +*.md +test/ diff --git a/backend/.env.example b/backend/.env.example index e7bd30e..9531f6b 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,9 +1,27 @@ # Familiarise Backend Environment Configuration -# Copy this file to .env and fill in the values - -# Database (Supabase PostgreSQL) -DATABASE_URL=postgresql://user:password@host:6543/postgres?pgbouncer=true -DIRECT_URL=postgresql://user:password@host:5432/postgres +# Copy this file to .env and fill in the values. +# +# ── Precedence ─────────────────────────────────────────────────────────────── +# main.dart resolves configuration in this order, last one winning: +# .env → .env.local → the process environment +# So an exported variable (or a docker compose `environment:` entry) always +# beats this file. That is what makes the database switchable without editing +# anything. +# +# ── Database ───────────────────────────────────────────────────────────────── +# You normally do NOT need to set the two URLs below. +# +# docker compose up → containers get the local Postgres +# source scripts/use-db.sh local → native dart_frog dev, local Postgres +# source scripts/use-db.sh supabase → native, cloud DB (see .env.supabase.example) +# +# Set them here only if you want a fixed default for every native run. Leaving +# a stale cloud URL here is the classic way to run destructive tests against +# shared data by accident — the startup log prints the resolved host on every +# boot so you can catch it. +# +# DATABASE_URL=postgresql://user:password@host:6543/postgres?pgbouncer=true +# DIRECT_URL=postgresql://user:password@host:5432/postgres # JWT Secret (generate a secure random string) JWT_SECRET=your-jwt-secret-here @@ -36,5 +54,9 @@ STRIPE_SECRET_KEY=sk_test_xxxxx # Webhook signing secret from Stripe Dashboard -> Webhooks STRIPE_WEBHOOK_SECRET=whsec_xxxxx +# PAN encryption key (required by the consultant tax-info routes). +# 32 bytes, hex-encoded — generate with: openssl rand -hex 32 +PAN_ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000 + # Server PORT=8080 diff --git a/backend/.env.supabase.example b/backend/.env.supabase.example new file mode 100644 index 0000000..2699813 --- /dev/null +++ b/backend/.env.supabase.example @@ -0,0 +1,25 @@ +# Template for backend/.env.supabase — the shared cloud database. +# +# Copy to backend/.env.supabase (gitignored) and fill in the real values, then: +# +# source scripts/use-db.sh supabase # native dart_frog dev +# FAM_DIRECT_URL="$DIRECT_URL" docker compose up # containers +# +# Local development does NOT need this file. The default everywhere is the +# docker compose Postgres; this exists only for the times you must reproduce +# something against real data. +# +# ⚠️ This is a shared database. Anything you write is visible to everyone and +# anything you delete is gone. Prefer `source scripts/use-db.sh local`. +# backend/prisma/sql/seed-dev.sql and docker/db-init refuse to touch any +# host that is not local, precisely so this cannot be provisioned by +# accident. + +# Session-mode pooler (port 5432). The backend uses DIRECT_URL because +# prisma_flutter_connector relies on prepared statements, which PgBouncer's +# transaction mode does not support. +DIRECT_URL=postgresql://USER:PASSWORD@aws-0-REGION.pooler.supabase.com:5432/postgres + +# Transaction-mode pooler (port 6543). Kept for parity with the deployed +# configuration; the backend prefers DIRECT_URL when both are set. +DATABASE_URL=postgresql://USER:PASSWORD@aws-0-REGION.pooler.supabase.com:6543/postgres?pgbouncer=true diff --git a/backend/.gitignore b/backend/.gitignore index 5adbfeb..8fbc280 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -6,7 +6,13 @@ # Files and directories created by pub .dart_tool/ .packages -pubspec.lock +# NOTE: pubspec.lock is deliberately NOT ignored. This is an application +# package (publish_to: none), so the lockfile belongs in version control — +# without it Docker builds and CI resolve fresh every time, which makes them +# non-reproducible and lets a caret-range bump (e.g. freezed 2.x -> 3.x, a +# breaking generator change) break the build with no code change. It is also +# an input to backend/scripts/ensure-generated.sh's codegen hash. +# The root pubspec.lock has always been tracked; this makes the two consistent. # Files and directories created by dart_frog build/ @@ -16,7 +22,12 @@ build/ coverage/ # Environment files +# .env.supabase holds real cloud credentials; .env.local is the per-developer +# override layer. Only the *.example templates are tracked. .env +.env.* +!.env.example +!.env.*.example # ============================================ # Code Generation (regenerate with commands below) diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..97c2ee5 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,82 @@ +# Familiarise Mobile API — Production Dockerfile +# +# IMPORTANT BUILD NOTES (learned from failed deployments): +# +# 1. BASE IMAGE: Must use Flutter image, NOT dart:stable. +# prisma_flutter_connector depends on the Flutter SDK for dependency +# resolution. Using dart:stable causes "flutter from sdk doesn't exist" +# during `dart pub get`. +# +# 2. PRISMA CODEGEN: lib/generated/ is gitignored (PR #108), so Railway +# never receives the Prisma-generated types. The Dockerfile must regenerate +# them via `dart run prisma_flutter_connector:generate`. +# +# 3. BUILD_RUNNER: After Prisma generation, freezed/json_serializable must +# also run. Without this, the AOT compile fails with "Not a constant +# expression" and undefined type errors in repositories. +# +# 4. PRISMA SCHEMA: backend/prisma/schema.prisma was originally a symlink +# to ../../familiarise_web/prisma/schema.prisma. Docker cannot follow +# symlinks that point outside the build context. The symlink was replaced +# with a real file copy. If the web schema changes, re-copy it: +# cp ~/Desktop/familiarise_web/prisma/schema.prisma backend/prisma/schema.prisma +# +# 5. RUNTIME IMAGE: Cannot use `FROM scratch` with the Flutter build image. +# The official dart:stable image provides /runtime/ (libc + friends) for +# scratch-based final stages, but the Flutter image does not. AOT-compiled +# Dart binaries need libc, so we use debian:bookworm-slim instead. +# +# 6. LOCAL TESTING: Always test locally before deploying to Railway: +# docker build -t familiarise-mobile-api . +# docker run -p 8080:8080 --env-file .env familiarise-mobile-api +# curl http://localhost:8080/api/health +# +# Build steps mirror: scripts/regenerate-build.sh --backend +# ───────────────────────────────────────────────────────────── + +# ── Stage 1: Build ────────────────────────────────────────── +FROM ghcr.io/cirruslabs/flutter:stable AS build + +WORKDIR /app + +# Install dart_frog_cli for the build step +RUN dart pub global activate dart_frog_cli +ENV PATH="/root/.pub-cache/bin:${PATH}" + +# Copy and resolve dependencies first (Docker layer caching) +COPY pubspec.* ./ +RUN flutter pub get + +# Copy full source +COPY . . +RUN flutter pub get --offline + +# 1. Generate Prisma client (lib/generated/ is gitignored, must regenerate) +RUN dart run prisma_flutter_connector:generate \ + --schema prisma/schema.prisma \ + --output lib/generated \ + --server + +# 2. Run build_runner for freezed/json codegen on generated models +RUN dart run build_runner build --delete-conflicting-outputs + +# 3. Generate Dart Frog build output (creates build/bin/server.dart) +RUN dart_frog build + +# 4. AOT compile the generated server to a native binary +RUN dart compile exe build/bin/server.dart -o build/bin/server + +# ── Stage 2: Runtime ──────────────────────────────────────── +# Use debian:bookworm-slim (NOT scratch) — the Flutter build image does not +# provide /runtime/ like dart:stable does. AOT binaries need libc + friends. +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"] diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..b995561 --- /dev/null +++ b/backend/Dockerfile.dev @@ -0,0 +1,43 @@ +# Development image for the Dart Frog API — hot reload, used by docker-compose. +# +# This is NOT the deployment image. See ./Dockerfile for that; its header +# comments document the constraints both images share, in particular: +# +# The base MUST be a Flutter image, not dart:stable. prisma_flutter_connector +# declares `flutter: sdk: flutter`, so `pub get` fails on a Dart-only SDK with +# "flutter from sdk doesn't exist". +# +# Deliberately does NO code generation at build time. lib/generated is ~735k +# lines rebuilt from prisma/schema.prisma; baking it into a layer would mean +# re-running it on every image rebuild. Instead docker-compose keeps +# lib/generated and .dart_tool in named volumes and the entrypoint regenerates +# only when the inputs actually change (see backend/scripts/ensure-generated.sh). + +FROM ghcr.io/cirruslabs/flutter:stable + +WORKDIR /app + +# curl: used by the compose healthcheck. +# postgresql-client: lets you psql the database from inside the container. +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +RUN dart pub global activate dart_frog_cli +ENV PATH="/root/.pub-cache/bin:${PATH}" + +# Copied to /usr/local/bin rather than left in /app, because compose +# bind-mounts ./backend over /app and would shadow anything placed there. +COPY docker/dev-entrypoint.sh /usr/local/bin/dev-entrypoint +RUN chmod +x /usr/local/bin/dev-entrypoint + +EXPOSE 8080 + +ENTRYPOINT ["/usr/local/bin/dev-entrypoint"] + +# --hostname 0.0.0.0 is required for the published port to be reachable from +# the host; dart_frog dev binds localhost otherwise. Verified supported in +# dart_frog_cli 1.2.14 (it must be a literal IP, not a hostname). +CMD ["dart_frog", "dev", "--hostname", "0.0.0.0", "--port", "8080"] diff --git a/backend/build.yaml b/backend/build.yaml new file mode 100644 index 0000000..1194be2 --- /dev/null +++ b/backend/build.yaml @@ -0,0 +1,40 @@ +# Backend code-generation scope. +# +# ── Why this file exists ───────────────────────────────────────────────────── +# +# json_serializable was running as a build phase over every Dart file in this +# package and producing NOTHING. There is not a single `part '*.g.dart'` +# directive under backend/lib or backend/routes, and no generated .freezed.dart +# references _$…FromJson — the Prisma connector emits hand-written +# fromJson/toJson bodies instead (see lib/generated/models/*.dart). +# +# The one .g.dart in the package, lib/generated/schema_registry.g.dart, is +# emitted by `prisma_flutter_connector:generate`, NOT by build_runner, so +# disabling json_serializable cannot affect it. +# +# Disabling it also removes two more phases for free: json_serializable +# `applies_builders: [source_gen|combining_builder]`, and combining_builder is +# `auto_apply: none` (so nothing else pulls it in) and itself applies +# source_gen|part_cleanup. It additionally drops freezed's +# `runs_before: [json_serializable]` ordering barrier, allowing more parallelism. +# +# If you ever add an @JsonSerializable class to the backend, set `enabled: true` +# below and delete this comment. + +targets: + $default: + builders: + json_serializable:json_serializable: + enabled: false + + freezed:freezed: + enabled: true + # Every @freezed class in this package is Prisma-generated. Scoping + # freezed to lib/generated stops it walking routes/ (132 files) and the + # hand-written parts of lib/ looking for annotations that cannot be + # there. + generate_for: + include: + - lib/generated/**.dart + exclude: + - lib/generated/**.freezed.dart diff --git a/backend/docker/dev-entrypoint.sh b/backend/docker/dev-entrypoint.sh new file mode 100755 index 0000000..50b2c5e --- /dev/null +++ b/backend/docker/dev-entrypoint.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Entrypoint for the API dev container. +# +# Runs before `dart_frog dev`. Everything here is designed to be a no-op on a +# warm container so `docker compose restart api` comes back in seconds rather +# than minutes. + +set -euo pipefail +cd /app +export PATH="/root/.pub-cache/bin:$PATH" + +# .dart_tool is a named volume, so it starts empty on a fresh volume even +# though pubspec.yaml is bind-mounted from the host. Resolve if needed. +if [ ! -f .dart_tool/package_config.json ]; then + echo "==> flutter pub get (cold volume)" + flutter pub get +fi + +# Hash-guarded: regenerates lib/generated only when schema.prisma, the +# connector/freezed versions, or build.yaml actually changed. On a warm +# container this prints "up to date" and exits immediately. +./scripts/ensure-generated.sh + +echo "==> starting: $*" +exec "$@" diff --git a/backend/main.dart b/backend/main.dart index d1632b1..1e7698a 100644 --- a/backend/main.dart +++ b/backend/main.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +import 'dart:io' show File, HttpServer, InternetAddress, Platform; import 'package:backend/database/database_client.dart'; import 'package:backend/services/auth/auth_service.dart'; @@ -14,8 +14,23 @@ import 'package:dotenv/dotenv.dart'; /// Server entry point /// Initializes database, services, and starts the HTTP server Future run(Handler handler, InternetAddress ip, int port) async { - // Load environment variables - final env = DotEnv()..load(['.env']); + // Environment resolution, lowest precedence first: + // 1. .env — shared local config (gitignored) + // 2. .env.local — per-developer override layer (gitignored), + // e.g. pointing DIRECT_URL at local Postgres + // 3. Platform.environment — ALWAYS WINS. This is what docker compose, + // Railway and CI inject. + // + // Note the deliberate ordering. DotEnv's own `includePlatformEnvironment` + // flag injects the environment in the *constructor*, and `load()` then does + // `_map.addAll(file)` on top — so the file would override the environment. + // That is backwards for containers: a stale DIRECT_URL in a bind-mounted + // .env would silently hijack a "local" run and point it at production. + // Applying Platform.environment last inverts that. + final env = DotEnv(); + if (File('.env').existsSync()) env.load(['.env']); + if (File('.env.local').existsSync()) env.load(['.env.local']); + env.addAll(Platform.environment); // Initialize Sentry for error tracking (optional) await SentryLogger.init(env['SENTRY_DSN']); @@ -25,12 +40,15 @@ Future run(Handler handler, InternetAddress ip, int port) async { // which the prisma_flutter_connector uses internally final databaseUrl = env['DIRECT_URL'] ?? env['DATABASE_URL']; if (databaseUrl == null) { - throw Exception('DIRECT_URL or DATABASE_URL must be set in .env'); + throw Exception( + 'DIRECT_URL or DATABASE_URL must be set in the environment or .env. ' + 'For local development run: source scripts/use-db.sh local', + ); } final jwtSecret = env['JWT_SECRET']; if (jwtSecret == null) { - throw Exception('JWT_SECRET must be set in .env'); + throw Exception('JWT_SECRET must be set in the environment or .env'); } // GitHub OAuth credentials (optional - only needed if using GitHub auth) @@ -49,7 +67,10 @@ Future run(Handler handler, InternetAddress ip, int port) async { } // Initialize database - SentryLogger.info('Connecting to database...', context: 'Startup'); + SentryLogger.info( + 'Connecting to database... (${_describeTarget(databaseUrl)})', + context: 'Startup', + ); final db = await DatabaseClient.initialize(databaseUrl); SentryLogger.info('Database connected successfully!', context: 'Startup'); @@ -116,3 +137,20 @@ Future run(Handler handler, InternetAddress ip, int port) async { ); return serve(handlerWithProviders, ip, port); } + +/// Renders the connection target of [databaseUrl] as `host:port/database`. +/// +/// Credentials are never included — this string is logged on every boot (and +/// forwarded to Sentry), so it must stay safe to read in a shared console. +/// +/// Its purpose is to make "am I about to run destructive tests against +/// production?" answerable at a glance: a local run must print `db:5432` (in +/// Docker) or `localhost:5433` (native), never a `*.supabase.com` host. +String _describeTarget(String databaseUrl) { + final uri = Uri.tryParse(databaseUrl); + if (uri == null || uri.host.isEmpty) return 'target='; + + final database = uri.pathSegments.isEmpty ? '?' : uri.pathSegments.first; + final port = uri.hasPort ? uri.port : 5432; + return 'host=${uri.host}:$port, db=$database'; +} diff --git a/backend/prisma/sql/check-constraints.sql b/backend/prisma/sql/check-constraints.sql new file mode 100644 index 0000000..d8f26c9 --- /dev/null +++ b/backend/prisma/sql/check-constraints.sql @@ -0,0 +1,186 @@ +-- ───────────────────────────────────────────────────────────── +-- VENDORED from familiarise_web (which owns the Prisma schema). +-- Do not edit here. To refresh after an upstream change: +-- cp ~/Desktop/familiarise_web/prisma/sql/check-constraints.sql \ +-- backend/prisma/sql/check-constraints.sql +-- +-- Applied by docker/db-init against the local Postgres. The upstream +-- `-- SPLIT` markers are plain comments here: psql runs the whole file +-- in one go, so no statement splitting is needed (they exist only +-- because Prisma $executeRawUnsafe takes one statement per call). +-- ───────────────────────────────────────────────────────────── + +-- #676 A1–A4 — data-integrity CHECK constraints for booking/payment tables. +-- +-- Prisma 7 has no @@check in PSL, so these ride the same sidecar pattern as +-- ledger-triggers.sql: idempotent statements split on `-- SPLIT`, applied via +-- `npm run db:constraints` after every push/reset (NOT against the shared dev +-- DB mid-cycle — the pre-MVP reset applies them to a clean schema). +-- +-- Payment amounts use >= 0, not > 0: credit-covered checkouts and +-- org-sponsored bookings legitimately write amount = 0 (free_/org_ synthetic +-- payment intents in lib/payments/operations/checkout.ts). + +ALTER TABLE "SlotOfAppointment" DROP CONSTRAINT IF EXISTS "slot_time_order"; +-- SPLIT +ALTER TABLE "SlotOfAppointment" ADD CONSTRAINT "slot_time_order" CHECK ("endsAt" > "startsAt"); +-- SPLIT +ALTER TABLE "Payment" DROP CONSTRAINT IF EXISTS "payment_amounts_nonnegative"; +-- SPLIT +ALTER TABLE "Payment" ADD CONSTRAINT "payment_amounts_nonnegative" CHECK ("amount" >= 0 AND "originalAmount" >= 0 AND "taxAmount" >= 0); +-- SPLIT +ALTER TABLE "ConsultationPlan" DROP CONSTRAINT IF EXISTS "consultation_plan_price_nonnegative"; +-- SPLIT +ALTER TABLE "ConsultationPlan" ADD CONSTRAINT "consultation_plan_price_nonnegative" CHECK ("price" >= 0); +-- SPLIT +ALTER TABLE "SubscriptionPlan" DROP CONSTRAINT IF EXISTS "subscription_plan_price_nonnegative"; +-- SPLIT +ALTER TABLE "SubscriptionPlan" ADD CONSTRAINT "subscription_plan_price_nonnegative" CHECK ("price" >= 0); +-- SPLIT +ALTER TABLE "WebinarPlan" DROP CONSTRAINT IF EXISTS "webinar_plan_price_nonnegative"; +-- SPLIT +ALTER TABLE "WebinarPlan" ADD CONSTRAINT "webinar_plan_price_nonnegative" CHECK ("price" >= 0); +-- SPLIT +ALTER TABLE "ClassPlan" DROP CONSTRAINT IF EXISTS "class_plan_price_nonnegative"; +-- SPLIT +ALTER TABLE "ClassPlan" ADD CONSTRAINT "class_plan_price_nonnegative" CHECK ("price" >= 0); +-- SPLIT +ALTER TABLE "WebinarPlan" DROP CONSTRAINT IF EXISTS "webinar_plan_max_participants_min"; +-- SPLIT +ALTER TABLE "WebinarPlan" ADD CONSTRAINT "webinar_plan_max_participants_min" CHECK ("maxParticipants" >= 1); +-- SPLIT +ALTER TABLE "ClassPlan" DROP CONSTRAINT IF EXISTS "class_plan_max_participants_min"; +-- SPLIT +ALTER TABLE "ClassPlan" ADD CONSTRAINT "class_plan_max_participants_min" CHECK ("maxParticipants" >= 1); + +-- SPLIT +-- #440 — DB-level double-booking backstop for 1:1 bookings. The application +-- guards (consultant allocation lock, #827 confirm-time recheck) are the +-- first line; this exclusion constraint is the last line: two CONFIRMED +-- slots for the same consultant may never overlap in time. Scoped to rows +-- carrying the denormalized consultantProfileId — consultation/subscription +-- slot creates set it; webinar/class attendee slots deliberately leave it +-- NULL (many same-window rows per event are legitimate there) and legacy +-- pre-#440 rows are NULL. tstzrange is '[)' so back-to-back slots don't +-- conflict. +CREATE EXTENSION IF NOT EXISTS btree_gist; +-- SPLIT +ALTER TABLE "SlotOfAppointment" DROP CONSTRAINT IF EXISTS "slot_no_confirmed_overlap"; +-- SPLIT +ALTER TABLE "SlotOfAppointment" ADD CONSTRAINT "slot_no_confirmed_overlap" + EXCLUDE USING gist ( + "consultantProfileId" WITH =, + tstzrange("startsAt", "endsAt") WITH && + ) + WHERE ("consultantProfileId" IS NOT NULL AND NOT "isTentative"); + +-- SPLIT +-- #747 / #685 — DB-enforced "at most one pending invite per (org, email)". +-- Prisma's `partialIndexes` is still preview at 7.7.0 (drift bugs +-- prisma/prisma#29263 / #29415), so the partial unique index ships via this +-- sidecar instead; the Serializable tx in invitations/route.ts stays as the +-- first line. lower(email): the accept flow compares case-insensitively and +-- the POST handler normalizes, so the index must not admit a mixed-case +-- duplicate from any other writer. Applied to a clean schema (pre-MVP reset) +-- — CREATE fails loudly if duplicate pending invites already exist, which is +-- the correct outcome. +DROP INDEX IF EXISTS "invitations_org_email_pending_key"; +-- SPLIT +CREATE UNIQUE INDEX "invitations_org_email_pending_key" + ON "invitations" ("organizationId", lower("email")) + WHERE "status" = 'pending'; + +-- SPLIT +-- #676 PM-17 — extend the payment_amounts_nonnegative pattern to every other +-- money-bearing table. Every paise column is non-negative (>= 0, matching +-- Payment). We deliberately do NOT use > 0: zero is legitimate across the +-- board — fully-refunded or credit/org-sponsored rows, LICENSE-funded +-- bookings (amount = 0), and refund/earnings reversal counter-entries that +-- net a row back to zero. NULLable columns (e.g. tdsAmountPaise, netAmount) +-- are exempted automatically: a CHECK passes when its operand is NULL. +ALTER TABLE "Refund" DROP CONSTRAINT IF EXISTS "refund_amount_nonnegative"; +-- SPLIT +ALTER TABLE "Refund" ADD CONSTRAINT "refund_amount_nonnegative" CHECK ("amountPaise" >= 0); +-- SPLIT +ALTER TABLE "Dispute" DROP CONSTRAINT IF EXISTS "dispute_amount_nonnegative"; +-- SPLIT +ALTER TABLE "Dispute" ADD CONSTRAINT "dispute_amount_nonnegative" CHECK ("amountPaise" >= 0); +-- SPLIT +ALTER TABLE "ConsultantPayout" DROP CONSTRAINT IF EXISTS "consultant_payout_amounts_nonnegative"; +-- SPLIT +ALTER TABLE "ConsultantPayout" ADD CONSTRAINT "consultant_payout_amounts_nonnegative" + CHECK ("amount" >= 0 AND "tdsDeducted" >= 0 AND ("netAmount" IS NULL OR "netAmount" >= 0)); +-- SPLIT +ALTER TABLE "OrganizationPayout" DROP CONSTRAINT IF EXISTS "org_payout_amounts_nonnegative"; +-- SPLIT +ALTER TABLE "OrganizationPayout" ADD CONSTRAINT "org_payout_amounts_nonnegative" + CHECK ( + "amountPaise" >= 0 + AND "grossRevenuePaise" >= 0 + AND "platformFeePaise" >= 0 + AND "refundsPaise" >= 0 + AND "netPayoutPaise" >= 0 + AND "clawbackAmountPaise" >= 0 + AND ("tdsAmountPaise" IS NULL OR "tdsAmountPaise" >= 0) + ); +-- SPLIT +ALTER TABLE "ConsultantEarnings" DROP CONSTRAINT IF EXISTS "consultant_earnings_amounts_nonnegative"; +-- SPLIT +ALTER TABLE "ConsultantEarnings" ADD CONSTRAINT "consultant_earnings_amounts_nonnegative" + CHECK ( + "grossAmount" >= 0 + AND "platformFeePaise" >= 0 + AND "consultantSharePaise" >= 0 + AND "refundedShareAmount" >= 0 + AND ("gstTcsAccruedPaise" IS NULL OR "gstTcsAccruedPaise" >= 0) + ); +-- SPLIT +ALTER TABLE "OrganizationEarnings" DROP CONSTRAINT IF EXISTS "org_earnings_amounts_nonnegative"; +-- SPLIT +ALTER TABLE "OrganizationEarnings" ADD CONSTRAINT "org_earnings_amounts_nonnegative" + CHECK ( + "grossAmountPaise" >= 0 + AND "platformFeePaise" >= 0 + AND "orgSharePaise" >= 0 + AND "consultantSharePaise" >= 0 + AND "refundedAmountPaise" >= 0 + ); + +-- SPLIT +-- #676 PM-18 — the 3-way split must never distribute more than it took in: +-- platform fee + org share + consultant share <= gross. earnings-service.ts +-- computes orgShare as the residual (gross - platformFee - consultantShare, +-- clamped to >= 0), so equality is the norm and this catches a future writer +-- that mis-derives the split. Applied to OrganizationEarnings only: the +-- ConsultantEarnings COLLABORATOR rows deliberately carry grossAmount = 0 +-- while consultantSharePaise > 0 (the booking gross lives once on the OWNER +-- row), so the same invariant does not hold there and must not be enforced. +ALTER TABLE "OrganizationEarnings" DROP CONSTRAINT IF EXISTS "org_earnings_split_within_gross"; +-- SPLIT +ALTER TABLE "OrganizationEarnings" ADD CONSTRAINT "org_earnings_split_within_gross" + CHECK ("platformFeePaise" + "orgSharePaise" + "consultantSharePaise" <= "grossAmountPaise"); + +-- SPLIT +-- #676 PM-22 — financial-year strings are always "YYYY-YY" (e.g. 2026-27). +-- Both writers persist the FY to avoid Apr-Mar boundary drift; this rejects a +-- malformed value at write time. ConsultantPayout.tdsFinancialYear is nullable +-- (payouts without a TDS deduction), so NULL is admitted. +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}$'); + +-- SPLIT +-- #784 — a Collaborator references exactly one plan: a webinar XOR a class. +-- The app-level backstop is assertCollaboratorPlanXor in +-- lib/collaborators/service.ts; this DB CHECK is the last line. Exactly one of +-- the two FKs is non-NULL <=> exactly one IS NULL, which `<>` expresses. +ALTER TABLE "Collaborator" DROP CONSTRAINT IF EXISTS "collaborator_plan_xor"; +-- SPLIT +ALTER TABLE "Collaborator" ADD CONSTRAINT "collaborator_plan_xor" + CHECK (("webinarPlanId" IS NULL) <> ("classPlanId" IS NULL)); diff --git a/backend/prisma/sql/ledger-triggers.sql b/backend/prisma/sql/ledger-triggers.sql new file mode 100644 index 0000000..d5759be --- /dev/null +++ b/backend/prisma/sql/ledger-triggers.sql @@ -0,0 +1,56 @@ +-- ───────────────────────────────────────────────────────────── +-- VENDORED from familiarise_web (which owns the Prisma schema). +-- Do not edit here. To refresh after an upstream change: +-- cp ~/Desktop/familiarise_web/prisma/sql/ledger-triggers.sql \ +-- backend/prisma/sql/ledger-triggers.sql +-- +-- Applied by docker/db-init against the local Postgres. The upstream +-- `-- SPLIT` markers are plain comments here: psql runs the whole file +-- in one go, so no statement splitting is needed (they exist only +-- because Prisma $executeRawUnsafe takes one statement per call). +-- ───────────────────────────────────────────────────────────── + +-- #776 — DB-level enforcement of the double-entry invariant Σ(DEBIT) == Σ(CREDIT) +-- per LedgerTransaction. The app-level guard in postLedgerTxn() is the first line; +-- this CONSTRAINT TRIGGER is the backstop that makes an unbalanced transaction +-- impossible to COMMIT even via raw SQL, a future code path, or a partial migration. +-- +-- DEFERRABLE INITIALLY DEFERRED so the check runs once at COMMIT — after all of a +-- transaction's entries are inserted — not after each row (a balanced txn is +-- transiently unbalanced mid-insert). +-- +-- prisma db push / migrate do NOT manage triggers, so this file is applied +-- separately: `npm run db:triggers` (idempotent) after every push/reset. See +-- docs/enterprise/10-money-and-ledger/13-ledger-integrity.md + docs/enterprise/90-audits/03-verification-guide.md. +-- +-- Statements are separated by `-- SPLIT` because Prisma's $executeRawUnsafe runs a +-- single statement per call (extended protocol); the apply script splits on it. + +DROP TRIGGER IF EXISTS ledger_txn_balanced ON "LedgerEntry"; +-- SPLIT +CREATE OR REPLACE FUNCTION assert_ledger_txn_balanced() RETURNS trigger AS $$ +DECLARE + txn_id text; + imbalance bigint; +BEGIN + txn_id := COALESCE(NEW."transactionId", OLD."transactionId"); + SELECT COALESCE( + SUM(CASE WHEN "direction" = 'DEBIT' THEN "amountPaise" ELSE -"amountPaise" END), + 0) + INTO imbalance + FROM "LedgerEntry" + WHERE "transactionId" = txn_id; + IF imbalance <> 0 THEN + RAISE EXCEPTION + 'Ledger transaction % is unbalanced by % paise (Sum(DEBIT) - Sum(CREDIT) != 0)', + txn_id, imbalance; + END IF; + RETURN NULL; +END; +$$ LANGUAGE plpgsql; +-- SPLIT +CREATE CONSTRAINT TRIGGER ledger_txn_balanced + AFTER INSERT OR UPDATE OR DELETE ON "LedgerEntry" + DEFERRABLE INITIALLY DEFERRED + FOR EACH ROW + EXECUTE FUNCTION assert_ledger_txn_balanced(); diff --git a/backend/prisma/sql/seed-dev.sql b/backend/prisma/sql/seed-dev.sql new file mode 100644 index 0000000..b9a34dc --- /dev/null +++ b/backend/prisma/sql/seed-dev.sql @@ -0,0 +1,689 @@ +-- ───────────────────────────────────────────────────────────── +-- Local development seed. +-- +-- HARVESTED from the `## Data Seeding` blocks of prompts/testing/unit/*.md, +-- which are the fixtures the E2E/agent test prompts already assume exist. +-- Regenerate with: ./scripts/gen-dev-seed.sh +-- +-- Each source file uses its own `test_unit__*` id prefix, so the blocks +-- are self-contained and safe to concatenate in file order. +-- +-- NOT idempotent on its own — docker/db-init/entrypoint.sh guards it with a +-- marker-row check so a re-run of `docker compose up` does not re-insert. +-- ───────────────────────────────────────────────────────────── + +BEGIN; + +-- ===== 01-auth.md ===== +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_auth_u1', 'Auth Unit User', 'test_unit_auth@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +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()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_auth_cp1', 'test_unit_auth_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_auth_cp1' WHERE id = 'test_unit_auth_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_auth_ck1', 'test_unit_auth_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_auth_np1', 'test_unit_auth_u1'); + +-- ===== 02-onboarding.md ===== +-- Fresh users with onboardingCompleted = false +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('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()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_onb_a1', 'test_unit_onb_cee', 'test_unit_onb_cee', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_onb_a2', 'test_unit_onb_cnt', 'test_unit_onb_cnt', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_onb_ck1', 'test_unit_onb_cee', true, NOW(), NOW()), + ('test_unit_onb_ck2', 'test_unit_onb_cnt', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_onb_np1', 'test_unit_onb_cee'), ('test_unit_onb_np2', 'test_unit_onb_cnt'); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_onb_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; +INSERT INTO "SubDomain" (id, name, "domainId", "createdAt", "updatedAt") +VALUES ('test_unit_onb_sub', 'Flutter Dev', 'test_unit_onb_dom', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +-- ===== 03-profile.md ===== +-- Verified consultant user +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, phone, "createdAt", "updatedAt") +VALUES ('test_unit_profile_u1', 'Profile Test User', 'test_unit_profile@test.com', true, 'CONSULTANT', true, 'Original bio', 'Mumbai', '+919000000001', NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_profile_a1', 'test_unit_profile_u1', 'test_unit_profile_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_profile_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_profile_cp1', 'test_unit_profile_u1', 'test_unit_profile_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_profile_cp1' WHERE id = 'test_unit_profile_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_profile_ck1', 'test_unit_profile_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_profile_np1', 'test_unit_profile_u1'); + +-- ===== 04-verification.md ===== +-- Consultant with PENDING_VERIFICATION status +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_verif_u1', 'Verif Test User', 'test_unit_verif@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_verif_a1', 'test_unit_verif_u1', 'test_unit_verif_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_verif_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_verif_cp1', 'test_unit_verif_u1', 'test_unit_verif_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_verif_cp1' WHERE id = 'test_unit_verif_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_verif_ck1', 'test_unit_verif_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_verif_np1', 'test_unit_verif_u1'); + +-- ===== 05-plans.md ===== +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_plans_u1', 'Plans Test Consultant', 'test_unit_plans@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_plans_a1', 'test_unit_plans_u1', 'test_unit_plans_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_plans_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_plans_cp1', 'test_unit_plans_u1', 'test_unit_plans_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_plans_cp1' WHERE id = 'test_unit_plans_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_plans_ck1', 'test_unit_plans_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_plans_np1', 'test_unit_plans_u1'); + +-- ===== 06-slots.md ===== +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_slots_u1', 'Slots Test Consultant', 'test_unit_slots@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_slots_a1', 'test_unit_slots_u1', 'test_unit_slots_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_slots_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_slots_cp1', 'test_unit_slots_u1', 'test_unit_slots_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_slots_cp1' WHERE id = 'test_unit_slots_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_slots_ck1', 'test_unit_slots_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_slots_np1', 'test_unit_slots_u1'); + +-- ===== 07-explore.md ===== +-- Consultant with plan + reviews +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, "createdAt", "updatedAt") +VALUES + ('test_unit_explore_u1', 'Explore Consultant', 'test_unit_explore_cnt@test.com', true, 'CONSULTANT', true, 'Expert Flutter developer', 'Bangalore', NOW(), NOW()), + ('test_unit_explore_u2', 'Explore Consultee', 'test_unit_explore_cee@test.com', true, 'CONSULTEE', true, NULL, NULL, NOW(), NOW()); + +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()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_explore_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", headline, rating, "createdAt", "updatedAt") +VALUES ('test_unit_explore_cp1', 'test_unit_explore_u1', 'test_unit_explore_dom', 'WEEKLY', true, 'VERIFIED', 'Flutter Expert', 4.5, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_explore_cp1' WHERE id = 'test_unit_explore_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_cep1', 'test_unit_explore_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_explore_cep1' WHERE id = 'test_unit_explore_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_plan1', 'Flutter Consultation', 'Learn Flutter basics', 1, 50000, 'test_unit_explore_cp1', NOW(), NOW()); + +-- Weekly availability slot +INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_slot1', 'MONDAY', 600, 'MONDAY', 660, 330, 'test_unit_explore_cp1', NOW(), NOW()); + +-- Review +INSERT INTO "ConsultantReview" (id, rating, "reviewDescription", "consultantProfileId", "consulteeProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_rev1', 5, 'Excellent mentor, very helpful!', 'test_unit_explore_cp1', 'test_unit_explore_cep1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_explore_ck1', 'test_unit_explore_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_explore_np1', 'test_unit_explore_u2'); + +-- ===== 08-booking.md ===== +-- Consultant with plan + slots +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_booking_u1', 'Booking Consultant', 'test_unit_booking_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_booking_u2', 'Booking Consultee', 'test_unit_booking_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_booking_a1', 'test_unit_booking_u1', 'test_unit_booking_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_booking_a2', 'test_unit_booking_u2', 'test_unit_booking_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_booking_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_booking_cp1', 'test_unit_booking_u1', 'test_unit_booking_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_booking_cp1' WHERE id = 'test_unit_booking_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_cep1', 'test_unit_booking_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_booking_cep1' WHERE id = 'test_unit_booking_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_plan1', 'Booking Test Plan', '1-hour consultation', 1, 50000, 'test_unit_booking_cp1', NOW(), NOW()); + +-- Weekly availability slot (Monday 10:00-11:00 IST) +INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_slot1', 'MONDAY', 270, 'MONDAY', 330, 330, 'test_unit_booking_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_booking_ck1', 'test_unit_booking_u1', true, NOW(), NOW()), + ('test_unit_booking_ck2', 'test_unit_booking_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_booking_np1', 'test_unit_booking_u1'), ('test_unit_booking_np2', 'test_unit_booking_u2'); + +-- ===== 09-checkout.md ===== +-- Consultant + consultee + booking in APPROVED_PENDING_PAYMENT state +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_checkout_u1', 'Checkout Consultant', 'test_unit_checkout_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_checkout_u2', 'Checkout Consultee', 'test_unit_checkout_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_checkout_a1', 'test_unit_checkout_u1', 'test_unit_checkout_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_checkout_a2', 'test_unit_checkout_u2', 'test_unit_checkout_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_checkout_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_cp1', 'test_unit_checkout_u1', 'test_unit_checkout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_checkout_cp1' WHERE id = 'test_unit_checkout_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_cep1', 'test_unit_checkout_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_checkout_cep1' WHERE id = 'test_unit_checkout_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_plan1', 'Checkout Test Plan', '1-hour session', 1, 75000, 'test_unit_checkout_cp1', NOW(), NOW()); + +-- Consultation in APPROVED_PENDING_PAYMENT status +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_con1', 'test_unit_checkout_plan1', 'APPROVED_PENDING_PAYMENT', 'test_unit_checkout_cep1', NOW(), NOW(), NOW()); + +-- Appointment for the consultation +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_apt1', 'CONSULTATION', 'test_unit_checkout_con1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_checkout_ck1', 'test_unit_checkout_u1', true, NOW(), NOW()), + ('test_unit_checkout_ck2', 'test_unit_checkout_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_checkout_np1', 'test_unit_checkout_u1'), ('test_unit_checkout_np2', 'test_unit_checkout_u2'); + +-- ===== 10-trials.md ===== +-- Consultant with subscription plan (freeTrialEnabled) + consultee +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_trials_u1', 'Trials Consultant', 'test_unit_trials_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_trials_u2', 'Trials Consultee', 'test_unit_trials_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_trials_a1', 'test_unit_trials_u1', 'test_unit_trials_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_trials_a2', 'test_unit_trials_u2', 'test_unit_trials_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_trials_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_trials_cp1', 'test_unit_trials_u1', 'test_unit_trials_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_trials_cp1' WHERE id = 'test_unit_trials_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_trials_cep1', 'test_unit_trials_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_trials_cep1' WHERE id = 'test_unit_trials_u2'; + +-- Subscription plan with free trial enabled +INSERT INTO "SubscriptionPlan" (id, title, description, "durationInMonths", price, "callsPerWeek", "sessionDurationInHours", "freeTrialEnabled", "freeTrialDurationMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_trials_sp1', 'Trial Subscription Plan', 'Monthly mentoring with free trial', 1, 200000, 1, 1, true, 30, 'test_unit_trials_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_trials_ck1', 'test_unit_trials_u1', true, NOW(), NOW()), + ('test_unit_trials_ck2', 'test_unit_trials_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_trials_np1', 'test_unit_trials_u1'), ('test_unit_trials_np2', 'test_unit_trials_u2'); + +-- ===== 11-waitlist.md ===== +-- Consultant + consultee + full webinar (maxParticipants=1, 1 enrolled) +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_waitlist_u1', 'Waitlist Consultant', 'test_unit_waitlist_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_waitlist_u2', 'Waitlist Consultee', 'test_unit_waitlist_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()), + ('test_unit_waitlist_u3', 'Waitlist Enrolled', 'test_unit_waitlist_enr@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_waitlist_a1', 'test_unit_waitlist_u1', 'test_unit_waitlist_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_waitlist_a2', 'test_unit_waitlist_u2', 'test_unit_waitlist_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_cp1', 'test_unit_waitlist_u1', 'test_unit_waitlist_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_waitlist_cp1' WHERE id = 'test_unit_waitlist_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_cep1', 'test_unit_waitlist_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_waitlist_cep1' WHERE id = 'test_unit_waitlist_u2'; + +-- Webinar plan with maxParticipants=1 (full capacity) +INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_wp1', 'Full Webinar', 'A sold-out webinar', 100000, 2, 1, 'test_unit_waitlist_cp1', NOW(), NOW()); + +-- Webinar instance +INSERT INTO "Webinar" (id, status, "webinarPlanId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_w1', 'SCHEDULED', 'test_unit_waitlist_wp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_waitlist_ck1', 'test_unit_waitlist_u1', true, NOW(), NOW()), + ('test_unit_waitlist_ck2', 'test_unit_waitlist_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_waitlist_np1', 'test_unit_waitlist_u1'), ('test_unit_waitlist_np2', 'test_unit_waitlist_u2'); + +-- ===== 12-documents.md ===== +-- Consultant + consultee with an appointment +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_docs_u1', 'Docs Consultant', 'test_unit_docs_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_docs_u2', 'Docs Consultee', 'test_unit_docs_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_docs_a1', 'test_unit_docs_u1', 'test_unit_docs_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_docs_a2', 'test_unit_docs_u2', 'test_unit_docs_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_docs_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_docs_cp1', 'test_unit_docs_u1', 'test_unit_docs_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_docs_cp1' WHERE id = 'test_unit_docs_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_cep1', 'test_unit_docs_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_docs_cep1' WHERE id = 'test_unit_docs_u2'; + +-- Consultation plan + consultation + appointment +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_plan1', 'Docs Test Plan', 1, 50000, 'test_unit_docs_cp1', NOW(), NOW()); + +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_docs_con1', 'test_unit_docs_plan1', 'SCHEDULED', 'test_unit_docs_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_apt1', 'CONSULTATION', 'test_unit_docs_con1', NOW(), NOW()); + +-- Seed an existing document +INSERT INTO "AppointmentDocument" (id, "fileName", "originalName", "fileSize", "mimeType", "fileUrl", "storagePath", description, "reviewStatus", "uploadedByRole", "appointmentId", "uploadedAt", "updatedAt") +VALUES ('test_unit_docs_doc1', 'resume.pdf', 'resume.pdf', 102400, 'application/pdf', 'https://example.com/resume.pdf', 'documents/test/resume.pdf', 'My resume for review', 'PENDING', 'CONSULTEE', 'test_unit_docs_apt1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_docs_ck1', 'test_unit_docs_u1', true, NOW(), NOW()), + ('test_unit_docs_ck2', 'test_unit_docs_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_docs_np1', 'test_unit_docs_u1'), ('test_unit_docs_np2', 'test_unit_docs_u2'); + +-- ===== 13-chat.md ===== +-- Simple user for chat UI test +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_chat_u1', 'Chat Test User', 'test_unit_chat@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_chat_a1', 'test_unit_chat_u1', 'test_unit_chat_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_chat_cep1', 'test_unit_chat_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_chat_cep1' WHERE id = 'test_unit_chat_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_chat_ck1', 'test_unit_chat_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_chat_np1', 'test_unit_chat_u1'); + +-- ===== 14-referrals.md ===== +-- User with referral code +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_u1', 'Referrals User', 'test_unit_referrals@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_referrals_a1', 'test_unit_referrals_u1', 'test_unit_referrals_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_cep1', 'test_unit_referrals_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_referrals_cep1' WHERE id = 'test_unit_referrals_u1'; + +-- Pre-existing referral code +INSERT INTO "ReferralCode" (id, "userId", code, "referrerReward", "refereeReward", "isActive", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_rc1', 'test_unit_referrals_u1', 'TESTREF123', 10000, 5000, true, NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_referrals_ck1', 'test_unit_referrals_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_referrals_np1', 'test_unit_referrals_u1'); + +-- ===== 15-reviews.md ===== +-- Consultant + consultee with completed appointment +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_reviews_u1', 'Reviews Consultant', 'test_unit_reviews_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_reviews_u2', 'Reviews Consultee', 'test_unit_reviews_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_reviews_a1', 'test_unit_reviews_u1', 'test_unit_reviews_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_reviews_a2', 'test_unit_reviews_u2', 'test_unit_reviews_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_reviews_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", rating, "createdAt", "updatedAt") +VALUES ('test_unit_reviews_cp1', 'test_unit_reviews_u1', 'test_unit_reviews_dom', 'WEEKLY', true, 'VERIFIED', 0, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_reviews_cp1' WHERE id = 'test_unit_reviews_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_cep1', 'test_unit_reviews_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_reviews_cep1' WHERE id = 'test_unit_reviews_u2'; + +-- Completed consultation + appointment +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_plan1', 'Review Test Plan', 1, 50000, 'test_unit_reviews_cp1', NOW(), NOW()); + +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_con1', 'test_unit_reviews_plan1', 'COMPLETED', 'test_unit_reviews_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_apt1', 'CONSULTATION', 'test_unit_reviews_con1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_reviews_ck1', 'test_unit_reviews_u1', true, NOW(), NOW()), + ('test_unit_reviews_ck2', 'test_unit_reviews_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_reviews_np1', 'test_unit_reviews_u1'), ('test_unit_reviews_np2', 'test_unit_reviews_u2'); + +-- ===== 16-support.md ===== +-- User with existing ticket +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_support_u1', 'Support User', 'test_unit_support@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_support_a1', 'test_unit_support_u1', 'test_unit_support_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_support_cep1', 'test_unit_support_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_support_cep1' WHERE id = 'test_unit_support_u1'; + +-- Existing support ticket +INSERT INTO "support_tickets" (id, title, description, priority, status, category, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_support_t1', 'Payment not received', 'I completed payment but status still shows pending', 'HIGH', 'OPEN', 'PAYMENT_FAILED', 'test_unit_support_u1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_support_ck1', 'test_unit_support_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_support_np1', 'test_unit_support_u1'); + +-- ===== 17-feedback.md ===== +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_feedback_u1', 'Feedback User', 'test_unit_feedback@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_feedback_a1', 'test_unit_feedback_u1', 'test_unit_feedback_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_feedback_cep1', 'test_unit_feedback_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_feedback_cep1' WHERE id = 'test_unit_feedback_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_feedback_ck1', 'test_unit_feedback_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_feedback_np1', 'test_unit_feedback_u1'); + +-- ===== 18-payout.md ===== +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_payout_u1', 'Payout Consultant', 'test_unit_payout@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_payout_a1', 'test_unit_payout_u1', 'test_unit_payout_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_payout_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_payout_cp1', 'test_unit_payout_u1', 'test_unit_payout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_payout_cp1' WHERE id = 'test_unit_payout_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_payout_ck1', 'test_unit_payout_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_payout_np1', 'test_unit_payout_u1'); + +-- ===== 19-tax.md ===== +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_tax_u1', 'Tax Consultant', 'test_unit_tax@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_tax_a1', 'test_unit_tax_u1', 'test_unit_tax_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_tax_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_tax_cp1', 'test_unit_tax_u1', 'test_unit_tax_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_tax_cp1' WHERE id = 'test_unit_tax_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_tax_ck1', 'test_unit_tax_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_tax_np1', 'test_unit_tax_u1'); + +-- ===== 20-staff.md ===== +-- Staff user +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u1', 'Staff User', 'test_unit_staff@test.com', true, 'STAFF', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_staff_a1', 'test_unit_staff_u1', 'test_unit_staff_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "StaffProfile" (id, "userId", department, position, "createdAt", "updatedAt") +VALUES ('test_unit_staff_sp1', 'test_unit_staff_u1', 'Support', 'Manager', NOW(), NOW()); +UPDATE "users" SET "staffProfileId" = 'test_unit_staff_sp1' WHERE id = 'test_unit_staff_u1'; + +-- Consultant with pending verification +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u2', 'Pending Consultant', 'test_unit_staff_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_staff_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_staff_cp1', 'test_unit_staff_u2', 'test_unit_staff_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_staff_cp1' WHERE id = 'test_unit_staff_u2'; + +-- Pending verification request +INSERT INTO "ConsultantProfileVerification" (id, status, "consultantProfileId", "submittedAt", notes, "createdAt", "updatedAt") +VALUES ('test_unit_staff_pv1', 'PENDING', 'test_unit_staff_cp1', NOW(), 'Please verify my credentials', NOW(), NOW()); + +-- User with open support ticket +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u3', 'Ticket User', 'test_unit_staff_tkt@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_cep1', 'test_unit_staff_u3', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_staff_cep1' WHERE id = 'test_unit_staff_u3'; + +INSERT INTO "support_tickets" (id, title, description, priority, status, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_t1', 'Staff Test Ticket', 'Need help with booking issue', 'HIGH', 'OPEN', 'test_unit_staff_u3', NOW(), NOW()); + +-- Pending feedback +INSERT INTO "feedbacks" (id, title, description, rating, status, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_fb1', 'Staff Test Feedback', 'Great platform but needs improvement', 4, 'PENDING', 'test_unit_staff_u3', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_staff_ck1', 'test_unit_staff_u1', true, NOW(), NOW()), + ('test_unit_staff_ck2', 'test_unit_staff_u2', true, NOW(), NOW()), + ('test_unit_staff_ck3', 'test_unit_staff_u3', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_staff_np1', 'test_unit_staff_u1'), ('test_unit_staff_np2', 'test_unit_staff_u2'), ('test_unit_staff_np3', 'test_unit_staff_u3'); + +-- ===== 21-announcements.md ===== +-- Active announcement +INSERT INTO "announcements" (id, title, content, "isActive", "startDate", "endDate", "backgroundColor", "textColor", "linkUrl", "linkText", "createdBy", "createdAt", "updatedAt") +VALUES ('test_unit_announce_1', 'Platform Update', 'We are rolling out new features this week! Check out the new booking flow.', true, NOW() - INTERVAL '1 day', NOW() + INTERVAL '7 days', '#1E40AF', '#FFFFFF', 'https://familiarise.com/updates', 'Learn More', 'system', NOW(), NOW()); + +-- User to view the announcement +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_announce_u1', 'Announce User', 'test_unit_announce@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_announce_a1', 'test_unit_announce_u1', 'test_unit_announce_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_announce_cep1', 'test_unit_announce_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_announce_cep1' WHERE id = 'test_unit_announce_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_announce_ck1', 'test_unit_announce_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_announce_np1', 'test_unit_announce_u1'); + +-- ===== 22-collaborations.md ===== +-- Two consultants: one invites the other to collaborate on a webinar +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_collab_u1', 'Collab Host', 'test_unit_collab_host@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_collab_u2', 'Collab Invited', 'test_unit_collab_inv@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_collab_a1', 'test_unit_collab_u1', 'test_unit_collab_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_collab_a2', 'test_unit_collab_u2', 'test_unit_collab_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_collab_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES + ('test_unit_collab_cp1', 'test_unit_collab_u1', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()), + ('test_unit_collab_cp2', 'test_unit_collab_u2', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp1' WHERE id = 'test_unit_collab_u1'; +UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp2' WHERE id = 'test_unit_collab_u2'; + +-- Webinar plan owned by host +INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_collab_wp1', 'Collab Webinar', 'A joint webinar on Flutter', 100000, 2, 50, 'test_unit_collab_cp1', NOW(), NOW()); + +-- Collaboration invitation (PENDING) +INSERT INTO "WebinarCollaborator" (id, "consultantProfileId", "webinarPlanId", role, "revenueSharePercentage", status, "invitedById", "createdAt", "updatedAt") +VALUES ('test_unit_collab_wc1', 'test_unit_collab_cp2', 'test_unit_collab_wp1', 'CO_HOST', 30.0, 'PENDING', 'test_unit_collab_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_collab_ck1', 'test_unit_collab_u1', true, NOW(), NOW()), + ('test_unit_collab_ck2', 'test_unit_collab_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_collab_np1', 'test_unit_collab_u1'), ('test_unit_collab_np2', 'test_unit_collab_u2'); + +-- ===== 23-dashboard.md ===== +-- Consultant with stats/earnings/pending requests +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_dash_u1', 'Dash Consultant', 'test_unit_dash_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_dash_a1', 'test_unit_dash_u1', 'test_unit_dash_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_dash_a2', 'test_unit_dash_u2', 'test_unit_dash_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_dash_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "totalRevenue", "pendingRevenue", rating, "createdAt", "updatedAt") +VALUES ('test_unit_dash_cp1', 'test_unit_dash_u1', 'test_unit_dash_dom', 'WEEKLY', true, 'VERIFIED', 500000, 100000, 4.5, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_dash_cp1' WHERE id = 'test_unit_dash_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_cep1', 'test_unit_dash_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_dash_cep1' WHERE id = 'test_unit_dash_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_plan1', 'Dashboard Plan', 1, 50000, 'test_unit_dash_cp1', NOW(), NOW()); + +-- Pending consultation (shows as pending request for consultant) +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_dash_con1', 'test_unit_dash_plan1', 'PENDING', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); + +-- Scheduled consultation (shows as upcoming for both) +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_dash_con2', 'test_unit_dash_plan1', 'SCHEDULED', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_apt1', 'CONSULTATION', 'test_unit_dash_con2', NOW(), NOW()); + +-- Upcoming slot +INSERT INTO "SlotOfAppointment" (id, "startsAt", "endsAt", "appointmentId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_soa1', NOW() + INTERVAL '2 days', NOW() + INTERVAL '2 days' + INTERVAL '1 hour', 'test_unit_dash_apt1', NOW(), NOW()); + +-- Activity log +INSERT INTO "ActivityLog" (id, "activityType", description, "actorId", "actorName", "consultantProfileId", "createdAt") +VALUES ('test_unit_dash_al1', 'CONSULTATION_BOOKED', 'Dash Consultee booked a consultation', 'test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cp1', NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_dash_ck1', 'test_unit_dash_u1', true, NOW(), NOW()), + ('test_unit_dash_ck2', 'test_unit_dash_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_dash_np1', 'test_unit_dash_u1'), ('test_unit_dash_np2', 'test_unit_dash_u2'); + +COMMIT; diff --git a/backend/routes/api/health.dart b/backend/routes/api/health.dart new file mode 100644 index 0000000..5c0d13f --- /dev/null +++ b/backend/routes/api/health.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:dart_frog/dart_frog.dart'; + +/// GET /api/health +/// Railway uses this endpoint to verify the server is running. +Response onRequest(RequestContext context) { + if (context.request.method != HttpMethod.get) { + return Response(statusCode: HttpStatus.methodNotAllowed); + } + + return Response.json( + body: { + 'status': 'ok', + 'timestamp': DateTime.now().toIso8601String(), + 'service': 'familiarise-mobile-api', + }, + ); +} diff --git a/backend/scripts/ensure-generated.sh b/backend/scripts/ensure-generated.sh new file mode 100755 index 0000000..1b65d8e --- /dev/null +++ b/backend/scripts/ensure-generated.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# Regenerate backend codegen only when its inputs actually changed. +# +# ── The problem this solves ────────────────────────────────────────────────── +# +# scripts/regenerate-build.sh used to `rm -rf lib/generated` and delete every +# *.freezed.dart unconditionally, on every invocation. lib/generated is ~735k +# lines across 498 files, so that destroyed build_runner's asset graph and +# forced a full cold rebuild every single time — even when nothing had changed, +# which is the overwhelmingly common case (schema.prisma is launch-frozen). +# +# Only four things can invalidate the output: +# - prisma/schema.prisma (what the models are) +# - prisma_flutter_connector version (the generator's output shape) +# - freezed version (the .freezed.dart output shape) +# - build.yaml (which builders run, over what) +# +# So hash those, stamp the result, and skip when it matches. +# +# Usage: +# ./scripts/ensure-generated.sh # regenerate if stale +# FORCE_CODEGEN=1 ./scripts/ensure-generated.sh # always regenerate + +set -euo pipefail +cd "$(dirname "$0")/.." + +FORCE="${FORCE_CODEGEN:-0}" +# The stamp lives inside lib/generated so that `rm -rf lib/generated` — by this +# script or by hand — also invalidates it. A stamp outside would survive the +# wipe and wrongly report "up to date" against an empty directory. +STAMP="lib/generated/.codegen-stamp" + +sha() { shasum -a 256 | cut -d' ' -f1; } + +# Read a package's resolved version out of pubspec.lock without needing yq. +locked() { + awk -v pkg=" $1:" ' + $0 == pkg { f = 1; next } + f && /^ version:/ { gsub(/"/, "", $2); print $2; exit } + f && /^ [a-z_]+:/ { exit } + ' pubspec.lock +} + +[ -f pubspec.lock ] || dart pub get + +WANT="$( { + shasum -a 256 prisma/schema.prisma + echo "connector=$(locked prisma_flutter_connector)" + echo "freezed=$(locked freezed)" + [ -f build.yaml ] && shasum -a 256 build.yaml +} | sha )" + +if [ "$FORCE" != "1" ] \ + && [ -f "$STAMP" ] \ + && [ "$(cat "$STAMP")" = "$WANT" ] \ + && [ -f lib/generated/schema_registry.g.dart ]; then + echo "[codegen] up to date (${WANT:0:12}) — skipping" + exit 0 +fi + +echo "[codegen] stale or forced — regenerating" +dart pub get + +# Full wipe ONLY on a real input change. +rm -rf lib/generated + +dart run prisma_flutter_connector:generate \ + --schema prisma/schema.prisma \ + --output lib/generated \ + --server + +dart run build_runner build --delete-conflicting-outputs + +echo "$WANT" > "$STAMP" +echo "[codegen] done (${WANT:0:12})" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..da31e93 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,160 @@ +# Local development stack. +# +# docker compose up → everything (Postgres + API + Flutter web) +# docker compose up db-init → Postgres only, provisioned (the fast path) +# docker compose down → stop, KEEP the database and build caches +# docker compose down -v → stop and destroy them (full cold rebuild next time) +# +# ── Two things in here are load-bearing; do not "clean them up" ────────────── +# +# 1. `?sslmode=disable` on every local DIRECT_URL. +# DatabaseClient.initialize() only treats `localhost` / `127.0.0.1` as local +# and otherwise defaults to SslMode.require. Inside this network the host is +# `db`, so without the explicit parameter the backend attempts TLS against a +# plain postgres image and the handshake fails. +# +# 2. The `FAM_` prefix on every interpolated variable. +# Compose automatically reads the repo-root .env for interpolation, and that +# file contains DATABASE_URL / DIRECT_URL pointing at production Supabase. +# Namespacing means nothing in it can silently reach a container. Never +# reference a bare ${DATABASE_URL} or ${DIRECT_URL} here. +# +# DART_ENV is also deliberately unset: routes/_middleware.dart switches to +# production CORS when it is `production`, which would block localhost:3000. + +name: familiarise + +services: + # ── Postgres ─────────────────────────────────────────────────────────────── + db: + image: postgres:17-alpine + environment: + POSTGRES_USER: familiarise + POSTGRES_PASSWORD: familiarise + POSTGRES_DB: familiarise + # Durability traded for speed. This database is disposable: every byte in + # it is reproducible from `prisma db push` + the seed. + command: > + postgres + -c fsync=off + -c synchronous_commit=off + -c full_page_writes=off + -c max_connections=200 + -c shared_buffers=256MB + ports: + # 5433 on the host: 5432 is often taken by a Homebrew postgres. + - "${FAM_PG_PORT:-5433}:5432" + volumes: + # NAMED, never a bind mount — a Postgres data directory on macOS + # virtiofs is both dramatically slower and a corruption risk. + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U familiarise -d familiarise"] + interval: 3s + timeout: 3s + retries: 30 + start_period: 5s + + # ── Schema provisioning (one-shot, exits 0) ──────────────────────────────── + db-init: + build: + context: ./docker/db-init + image: familiarise/db-init:local + depends_on: + db: + condition: service_healthy + environment: + DIRECT_URL: ${FAM_DIRECT_URL:-postgresql://familiarise:familiarise@db:5432/familiarise?sslmode=disable} + volumes: + # Read-only: the in-repo schema and SQL stay the single source of truth. + - ./backend/prisma:/work/prisma:ro + restart: "no" + + # ── Dart Frog API (hot reload) ───────────────────────────────────────────── + api: + build: + context: ./backend + dockerfile: Dockerfile.dev + image: familiarise/api:dev + depends_on: + db-init: + condition: service_completed_successfully + environment: + DIRECT_URL: ${FAM_DIRECT_URL:-postgresql://familiarise:familiarise@db:5432/familiarise?sslmode=disable} + DATABASE_URL: ${FAM_DIRECT_URL:-postgresql://familiarise:familiarise@db:5432/familiarise?sslmode=disable} + PORT: "8080" + APP_BASE_URL: http://localhost:3000 + # Dev-only defaults. Override via FAM_* to exercise the real integrations. + JWT_SECRET: ${FAM_JWT_SECRET:-dev-only-jwt-secret-do-not-use-in-production} + PAN_ENCRYPTION_KEY: ${FAM_PAN_ENCRYPTION_KEY:-0000000000000000000000000000000000000000000000000000000000000000} + SENTRY_DSN: ${FAM_SENTRY_DSN:-} + SUPABASE_URL: ${FAM_SUPABASE_URL:-} + SUPABASE_SERVICE_ROLE_KEY: ${FAM_SUPABASE_SERVICE_ROLE_KEY:-} + STREAM_API_KEY: ${FAM_STREAM_API_KEY:-} + STREAM_API_SECRET: ${FAM_STREAM_API_SECRET:-} + RAZORPAY_KEY_ID: ${FAM_RAZORPAY_KEY_ID:-} + RAZORPAY_KEY_SECRET: ${FAM_RAZORPAY_KEY_SECRET:-} + RAZORPAY_WEBHOOK_SECRET: ${FAM_RAZORPAY_WEBHOOK_SECRET:-} + STRIPE_SECRET_KEY: ${FAM_STRIPE_SECRET_KEY:-} + STRIPE_WEBHOOK_SECRET: ${FAM_STRIPE_WEBHOOK_SECRET:-} + ports: + - "8080:8080" + volumes: + # Source is bind-mounted so edits hot-reload... + - ./backend:/app + # ...but every heavy generated/cache directory is shadowed by a named + # volume so it lives on the VM's native filesystem instead of virtiofs. + # For .dart_tool this is not merely an optimisation: package_config.json + # holds absolute /Users/... paths that do not exist inside the container. + - api_dart_tool:/app/.dart_tool + - api_generated:/app/lib/generated + - api_dart_frog:/app/.dart_frog + - api_build:/app/build + - pub_cache:/root/.pub-cache + healthcheck: + test: ["CMD-SHELL", "curl -fsS http://localhost:8080/api/health || exit 1"] + interval: 10s + timeout: 5s + retries: 90 + # First boot regenerates ~735k lines of Dart. Be generous. + start_period: 600s + stdin_open: true + tty: true + + # ── Flutter web ──────────────────────────────────────────────────────────── + # Slowest and least essential part of the stack — see docs/local-dev.md. + # If it drags, drop it: `docker compose up db-init api`. + web: + build: + context: . + dockerfile: docker/web/Dockerfile + image: familiarise/web:dev + depends_on: + api: + condition: service_healthy + environment: + # Baked into env_config.g.dart by envied at codegen time. This must be + # correct from the BROWSER's point of view (the browser runs on the + # host), so it is the published host port — not http://api:8080. + FAM_API_BASE_URL: ${FAM_API_BASE_URL:-http://localhost:8080} + ports: + - "3000:3000" + volumes: + - ./lib:/app/lib + - ./web:/app/web + - ./assets:/app/assets + - web_dart_tool:/app/.dart_tool + - web_build:/app/build + - pub_cache:/root/.pub-cache + stdin_open: true + tty: true + +volumes: + pgdata: + pub_cache: + api_dart_tool: + api_generated: + api_dart_frog: + api_build: + web_dart_tool: + web_build: diff --git a/docker/db-init/Dockerfile b/docker/db-init/Dockerfile new file mode 100644 index 0000000..38a1177 --- /dev/null +++ b/docker/db-init/Dockerfile @@ -0,0 +1,39 @@ +# One-shot provisioner for the local development Postgres. +# +# Deliberately NOT alpine: we need the `psql` client to apply the sidecar SQL +# (ledger triggers + CHECK constraints), and postgresql-client is a one-line +# install on bookworm. bookworm also ships OpenSSL 3, which is what Prisma's +# linux-*-openssl-3.0.x query engines expect. +# +# Prisma is pinned to 7.7.0 to match familiarise_web, which owns the schema. +# +# NOTE: no package.json and no prisma.config.ts. `prisma db push --schema X +# --url Y` reads neither a config file nor `datasource.url`, which is exactly +# what we need — backend/prisma/schema.prisma declares a datasource with no +# url (Prisma 7 moved those to config), and that file is owned by another PR +# and must not be edited here. + +FROM node:24-bookworm-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + postgresql-client \ + ca-certificates \ + openssl \ + && rm -rf /var/lib/apt/lists/* + +RUN npm install -g prisma@7.7.0 + +# Force the query/schema engine download into the image so `docker compose up` +# never hits the network. This also makes a missing arm64 engine fail the +# image BUILD with a clear message, rather than failing confusingly at runtime. +RUN prisma version + +WORKDIR /work + +COPY entrypoint.sh /usr/local/bin/db-init +RUN chmod +x /usr/local/bin/db-init + +# prisma/ (schema + sql/) is bind-mounted read-only by docker-compose, so the +# in-repo copies stay the single source of truth. + +ENTRYPOINT ["/usr/local/bin/db-init"] diff --git a/docker/db-init/entrypoint.sh b/docker/db-init/entrypoint.sh new file mode 100755 index 0000000..33b975d --- /dev/null +++ b/docker/db-init/entrypoint.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Provision the local development Postgres: +# 1. prisma db push — create the 129 tables from the in-repo schema +# 2. sidecar SQL — ledger triggers + CHECK constraints (db push +# does not manage triggers or checks) +# 3. seed-dev.sql — fixtures the test prompts assume, guarded so a +# repeat `docker compose up` does not re-insert +# +# Safe to run on every `docker compose up`: db push on an unchanged schema is a +# fast no-op and the sidecars are idempotent (DROP IF EXISTS + ADD). + +set -euo pipefail + +: "${DIRECT_URL:?DIRECT_URL must be set}" + +# ── Safety gate ──────────────────────────────────────────────── +# This script runs `db push --accept-data-loss`, which will happily drop +# columns to make the database match the schema. That is fine for a disposable +# local container and catastrophic anywhere else, so refuse to run against any +# host that is not the local compose network or loopback. +_rest=${DIRECT_URL#*://} # strip scheme +_rest=${_rest##*@} # strip user:password@ if present +DB_HOST=${_rest%%[:/?]*} # keep up to the first ':', '/' or '?' +case "$DB_HOST" in + db | localhost | 127.0.0.1 | postgres) ;; + *) + echo "REFUSING to provision: DIRECT_URL points at '$DB_HOST', which is not a" >&2 + echo "local database. db-init runs destructive schema pushes and must only" >&2 + echo "ever target the compose Postgres. Check your DIRECT_URL." >&2 + exit 1 + ;; +esac + +echo "==> Target: $DB_HOST" + +# ── 1. Schema ────────────────────────────────────────────────── +# --url bypasses both prisma.config.ts and datasource.url, so the in-repo +# schema (which declares neither) needs no edits. +# +# There is deliberately no --skip-generate: Prisma 7's `db push` does not +# invoke the generator at all, and passing the flag is a hard error. The +# schema's `generator client = prisma-client-js` block is therefore inert +# here — the Dart client comes from prisma_flutter_connector instead. +echo "==> [1/3] prisma db push" +prisma db push \ + --schema prisma/schema.prisma \ + --url "$DIRECT_URL" \ + --accept-data-loss + +# ── 2. Sidecars ──────────────────────────────────────────────── +# psql runs each file whole; the upstream `-- SPLIT` markers are just comments. +echo "==> [2/3] sidecar SQL (ledger triggers + CHECK constraints)" +psql "$DIRECT_URL" -v ON_ERROR_STOP=1 -q \ + -f prisma/sql/ledger-triggers.sql \ + -f prisma/sql/check-constraints.sql + +# ── 3. Seed ──────────────────────────────────────────────────── +# seed-dev.sql is a plain concatenation of the prompt fixtures and is not +# itself idempotent, so gate it on a marker row from the first block. +echo "==> [3/3] dev seed" +SEEDED=$(psql "$DIRECT_URL" -tAc \ + "SELECT count(*) FROM \"users\" WHERE id = 'test_unit_auth_u1'") + +if [ "$SEEDED" = "0" ]; then + psql "$DIRECT_URL" -v ON_ERROR_STOP=1 -q -f prisma/sql/seed-dev.sql + echo " seeded" +else + echo " already seeded, skipping" +fi + +TABLES=$(psql "$DIRECT_URL" -tAc \ + "SELECT count(*) FROM information_schema.tables WHERE table_schema='public'") +echo "==> Done. $TABLES tables in public schema." diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile new file mode 100644 index 0000000..fbc7257 --- /dev/null +++ b/docker/web/Dockerfile @@ -0,0 +1,31 @@ +# Flutter web dev server for docker-compose. Build context is the repo root. +# +# This is the slowest and least essential service in the stack (see +# docs/local-dev.md for measured expectations). It exists so that +# `docker compose up` brings up a working frontend at http://localhost:3000, +# matching the APP_URL the E2E prompts in prompts/testing/ already assume. +# +# Mobile is NOT built here and cannot be: an iOS simulator needs macOS + Xcode, +# which cannot be containerised, and the Android emulator needs KVM, which is +# unavailable inside Docker Desktop's VM. Mobile stays native on the host and +# talks to the same API on localhost:8080. + +FROM ghcr.io/cirruslabs/flutter:stable + +WORKDIR /app + +RUN flutter config --enable-web + +# Resolve dependencies in their own layer so source edits don't re-run pub. +COPY pubspec.yaml pubspec.lock ./ +RUN flutter pub get + +COPY docker/web/entrypoint.sh /usr/local/bin/web-entrypoint +RUN chmod +x /usr/local/bin/web-entrypoint + +EXPOSE 3000 + +ENTRYPOINT ["/usr/local/bin/web-entrypoint"] + +CMD ["flutter", "run", "-d", "web-server", \ + "--web-hostname", "0.0.0.0", "--web-port", "3000"] diff --git a/docker/web/entrypoint.sh b/docker/web/entrypoint.sh new file mode 100755 index 0000000..324351a --- /dev/null +++ b/docker/web/entrypoint.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# Entrypoint for the Flutter web dev container. + +set -euo pipefail +cd /app +export PATH="/root/.pub-cache/bin:$PATH" + +API_BASE_URL="${FAM_API_BASE_URL:-http://localhost:8080}" + +# envied reads .env at CODEGEN time and bakes the values into +# lib/core/config/env_config.g.dart — they are not read at runtime. So the file +# has to exist before build_runner, and API_BASE_URL has to be correct from the +# BROWSER's point of view. The browser runs on the host, so that is the +# published host port (http://localhost:8080), NOT the compose service name. +# +# Written fresh on every start rather than bind-mounted, so the container never +# depends on a host .env that may hold production values. +if [ ! -f .env ] || ! grep -q "^API_BASE_URL=${API_BASE_URL}$" .env 2>/dev/null; then + echo "==> writing .env (API_BASE_URL=${API_BASE_URL})" + cat > .env < flutter pub get (cold volume)" + flutter pub get +fi + +# envied/freezed/riverpod codegen. build_runner keeps its asset graph in the +# .dart_tool named volume, so this is incremental after the first run. +echo "==> build_runner" +dart run build_runner build --delete-conflicting-outputs + +echo "==> starting: $*" +exec "$@" diff --git a/docs/getting-started/04-local-testing-guide.md b/docs/getting-started/04-local-testing-guide.md index bd8f588..8128703 100644 --- a/docs/getting-started/04-local-testing-guide.md +++ b/docs/getting-started/04-local-testing-guide.md @@ -29,16 +29,31 @@ During development, the mobile app needs to communicate with the backend server. ## Local Development Setup +> **Docker is now the fastest way to get a full stack running** — Postgres, the +> API and Flutter web with one command, against a local database instead of the +> shared Supabase one. See +> [05-docker-local-dev.md](./05-docker-local-dev.md). +> +> ```bash +> make up # everything +> make db # just Postgres, if you prefer running the backend natively +> ``` + ### Starting the Backend Server -#### Development Mode (with hot reload) +#### Development Mode — hot reload (recommended) ```bash +source scripts/use-db.sh local # or `supabase` cd backend dart_frog dev ``` -> Note: May have terminal stdin issues in some environments +Edits to routes reload in place. Prefer this: the production-build path below +re-analyses every route and all ~735k lines of generated code on each change. + +> Note: may have terminal stdin issues in some non-interactive environments. -#### Production Build (recommended) +#### Production Build +Use when you need to reproduce the deployed binary exactly, not for iteration. ```bash cd backend dart_frog build diff --git a/docs/getting-started/05-docker-local-dev.md b/docs/getting-started/05-docker-local-dev.md new file mode 100644 index 0000000..14c2c4e --- /dev/null +++ b/docs/getting-started/05-docker-local-dev.md @@ -0,0 +1,177 @@ +# Docker Local Development + +Runs Postgres, the Dart Frog API and Flutter web locally, so the dev loop stops +depending on the shared Supabase database. + +## TL;DR + +```bash +make doctor # check the toolchain (Docker Desktop must be running) +make up # Postgres + API (:8080) + Flutter web (:3000) +``` + +If the containers feel slow on macOS, drop to the fast path — it delivers most +of the benefit: + +```bash +make db # Postgres only, schema pushed and seeded +source scripts/use-db.sh local # point the host at it +cd backend && dart_frog dev # native, full speed +``` + +## Why this exists + +Two separate problems, two separate fixes. + +**The database was remote.** `backend/.env` pointed at Supabase's Mumbai pooler, +about 40 ms away. `appointment_repository.dart` alone issues 121 queries; a +request touching twenty of them spent roughly 800 ms on network round-trips +before doing any work. A local Postgres answers in well under a millisecond. +It is also disposable, so destructive tests stop being scary. + +**Codegen was rebuilt from scratch every time.** `backend/lib/generated/` is +about 735,000 lines across 498 files, and it is gitignored. +`scripts/regenerate-build.sh` used to `rm -rf` it on every run, which threw away +build_runner's asset graph and forced a cold rebuild even when nothing had +changed. Docker cannot fix that — see [Codegen](#codegen) below for what does. + +## Requirements + +Docker Desktop with **at least 8 GB RAM, 4 CPUs and 60 GB disk**. The defaults +are too small: a Flutter web build and build_runner over 500 files will OOM. + +## What runs where + +| | Where | Why | +|---|---|---| +| Postgres | container, host port **5433** | 5432 is usually taken by a Homebrew postgres | +| Dart Frog API | container, **:8080** | `dart_frog dev`, hot reload | +| Flutter **web** | container, **:3000** | matches the `APP_URL` the E2E prompts assume | +| Flutter **mobile** | **host**, natively | see below | + +Mobile cannot be containerised. An iOS simulator needs macOS and Xcode, which +Docker cannot run at all; the Android emulator needs KVM, which is not available +inside Docker Desktop's VM. Keep using `./scripts/quick-ios.sh` and +`./scripts/quick-android.sh` — they talk to the same API on `localhost:8080`. + +## Choosing a database + +The backend resolves configuration as `.env` → `.env.local` → the process +environment, **last one winning**. So an exported variable always beats the +files, and nothing needs editing to switch. + +```bash +# Containers — the compose default is already the local Postgres. +docker compose up + +# 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 +``` + +On every boot the API logs the database it actually resolved: + +``` +INFO: [Startup] Connecting to database... (host=db:5432, db=familiarise) +``` + +**Read that line.** If it names a `*.supabase.com` host when you expected local, +stop — you are about to operate on shared data. Credentials are never printed. + +As a second layer, `docker/db-init` refuses to provision any host that is not +`db`, `postgres`, `localhost` or `127.0.0.1`, so a mistyped URL cannot cause it +to run `prisma db push --accept-data-loss` against production. + +## How the schema gets there + +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` (129 models, + 106 enums) using Prisma 7.7.0, matching `familiarise_web`. +2. `backend/prisma/sql/ledger-triggers.sql` and `check-constraints.sql` — + vendored from `familiarise_web`, because `db push` does not manage triggers + or CHECK constraints. Both are idempotent. +3. `backend/prisma/sql/seed-dev.sql` — the fixtures the test prompts already + assume, harvested from the `## Data Seeding` blocks in + `prompts/testing/unit/*.md`. Regenerate with `make seed-sql`. + +Steps 1 and 2 run on every `docker compose up` and are near no-ops when nothing +changed. Step 3 is skipped once a marker row exists. + +To refresh the vendored SQL after an upstream change: + +```bash +cp ~/Desktop/familiarise_web/prisma/sql/*.sql backend/prisma/sql/ +``` + +## Codegen + +This is where the minutes actually were. + +**`backend/build.yaml`** disables `json_serializable` for the backend. It was +running over every file and producing nothing — there is not one +`part '*.g.dart'` directive in `backend/lib` or `backend/routes`, because the +Prisma connector writes `fromJson`/`toJson` by hand. Disabling it also drops +`source_gen|combining_builder` and `part_cleanup`, and removes freezed's +`runs_before` ordering barrier. (`lib/generated/schema_registry.g.dart` comes +from the Prisma generator, not build_runner, so it is unaffected.) + +**`backend/scripts/ensure-generated.sh`** hashes `prisma/schema.prisma`, the +resolved `prisma_flutter_connector` and `freezed` versions, and `build.yaml`, +and skips regeneration when they all match the previous run. Since the schema is +launch-frozen, this is almost always a hit. + +```bash +make regen # skip if unchanged +make regen-force # wipe lib/generated and rebuild +``` + +Inside the container, `lib/generated` and `.dart_tool` live in named volumes, so +they survive `docker compose down` and the regen does not re-run on restart. +`docker compose down -v` destroys them and the next start is a full cold build. + +## Performance notes + +Named volumes shadow every heavy directory (`.dart_tool`, `lib/generated`, +`.dart_frog`, `build`, the pub cache) so they stay on the VM's native +filesystem. Only human-authored source crosses the macOS virtiofs boundary. + +For `.dart_tool` this is correctness, not tuning: `package_config.json` contains +absolute `/Users/...` paths that do not exist inside the container, so sharing +it between host and container is broken, not merely slow. + +Consequence: the container's `lib/generated` is a **different tree** from the +host's. Your IDE and `dart analyze` keep using the host copy. Run +`./scripts/regenerate-build.sh --prisma` on the host at least once. + +Flutter web in a container is the slowest and least essential piece — expect a +first compile of several minutes and hot restarts noticeably slower than native. +If it is not paying for itself, skip it with `make api` and run +`flutter run -d chrome` on the host. + +Note that the web container's build_runner writes `env_config.g.dart` back into +the bind-mounted `lib/`, so it will overwrite the host's copy with +`API_BASE_URL=http://localhost:8080`. That is the same value the host would +normally bake; if you have customised `PHYSICAL_DEVICE_API_URL` for a physical +device, rerun the host codegen afterwards. + +## Troubleshooting + +**Port already in use** — `make doctor` reports 5433/8080/3000. Override the +database port with `FAM_PG_PORT=5434 docker compose up`. + +**TLS/handshake error connecting to the database** — the URL lost its +`?sslmode=disable`. `DatabaseClient` treats only `localhost` and `127.0.0.1` as +local and otherwise requires TLS; inside compose the host is `db`, so the +parameter is required. + +**Hot reload stopped firing** — file-watch propagation over virtiofs is the +flakiest part of the stack. `docker compose restart api`, or fall back to the +native path. + +**`db-init` refuses to run** — it is telling you `DIRECT_URL` does not point at a +local database. That guard is deliberate; check the URL rather than removing it. diff --git a/scripts/dev-backend.sh b/scripts/dev-backend.sh new file mode 100755 index 0000000..a024428 --- /dev/null +++ b/scripts/dev-backend.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Run the backend natively with hot reload, against whichever database is +# currently selected. +# +# source scripts/use-db.sh local +# ./scripts/dev-backend.sh +# +# Why not just `cd backend && dart_frog dev`? +# +# Several call sites read Platform.environment directly rather than going +# through the DotEnv instance built in main.dart — storage_utils.dart, +# routes/api/upload/{image,document}.dart, route_handlers/user_reserved_handlers.dart +# (SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY), routes/_middleware.dart (DART_ENV, +# ALLOWED_ORIGINS), utils/slot_lock.dart (UPSTASH_REDIS_*), and others. Values +# that live only in backend/.env are invisible to those. Exporting the file into +# the process environment first makes the native run behave like the container, +# where compose supplies everything as real environment variables. + +set -euo pipefail +cd "$(dirname "$0")/../backend" + +# 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 + +if [ -z "${DIRECT_URL:-}${DATABASE_URL:-}" ]; then + echo "No DIRECT_URL/DATABASE_URL set." >&2 + echo "Run: source scripts/use-db.sh local" >&2 + exit 1 +fi + +./scripts/ensure-generated.sh + +exec "${HOME}/.pub-cache/bin/dart_frog" dev --port "${PORT:-8080}" diff --git a/scripts/gen-dev-seed.sh b/scripts/gen-dev-seed.sh new file mode 100755 index 0000000..4dd3c98 --- /dev/null +++ b/scripts/gen-dev-seed.sh @@ -0,0 +1,47 @@ +#!/bin/bash +# Regenerate backend/prisma/sql/seed-dev.sql from the test prompts. +# +# The `## Data Seeding` blocks in prompts/testing/unit/*.md are the fixtures the +# agent-driven test prompts already assume exist. Rather than maintain a second, +# drifting copy of that data, this script concatenates them into a single seed +# applied to the local Postgres by docker/db-init. +# +# Run after editing any prompts/testing/unit/*.md seeding block. + +set -euo pipefail +cd "$(dirname "$0")/.." + +OUT=backend/prisma/sql/seed-dev.sql +SRC_GLOB=prompts/testing/unit/*.md + +{ + cat <<'HDR' +-- ───────────────────────────────────────────────────────────── +-- Local development seed. +-- +-- HARVESTED from the `## Data Seeding` blocks of prompts/testing/unit/*.md, +-- which are the fixtures the E2E/agent test prompts already assume exist. +-- Regenerate with: ./scripts/gen-dev-seed.sh +-- +-- Each source file uses its own `test_unit__*` id prefix, so the blocks +-- are self-contained and safe to concatenate in file order. +-- +-- NOT idempotent on its own — docker/db-init/entrypoint.sh guards it with a +-- marker-row check so a re-run of `docker compose up` does not re-insert. +-- ───────────────────────────────────────────────────────────── + +BEGIN; +HDR + + for f in $SRC_GLOB; do + printf '\n-- ===== %s =====\n' "$(basename "$f")" + # Take the fenced ```sql block that follows the "## Data Seeding" heading. + # `-- execute_sql` is an agent directive, not SQL, so drop it. + awk '/^## Data Seeding/{flag=1;next} flag&&/^```sql/{inb=1;next} inb&&/^```/{exit} inb{print}' "$f" \ + | grep -v '^-- execute_sql$' + done + + printf '\nCOMMIT;\n' +} > "$OUT" + +echo "Wrote $OUT ($(wc -l < "$OUT" | tr -d ' ') lines, $(grep -c 'INSERT INTO\|^UPDATE' "$OUT") statements)" diff --git a/scripts/regenerate-build.sh b/scripts/regenerate-build.sh index f3904f4..45e4bbb 100755 --- a/scripts/regenerate-build.sh +++ b/scripts/regenerate-build.sh @@ -18,6 +18,7 @@ export PATH="$HOME/.pub-cache/bin:$PATH" RUN_PRISMA=false RUN_DARTFROG=false RUN_FLUTTER=false +FORCE=0 if [ $# -eq 0 ]; then RUN_PRISMA=true @@ -36,9 +37,17 @@ else --prisma) RUN_PRISMA=true ;; + --force) + FORCE=1 + ;; *) echo "Unknown option: $arg" - echo "Usage: $0 [--backend] [--frontend] [--prisma]" + echo "Usage: $0 [--backend] [--frontend] [--prisma] [--force]" + echo "" + echo " --force Wipe and regenerate even when inputs are unchanged." + echo " Without it, codegen is skipped when prisma/schema.prisma," + echo " the connector/freezed versions and build.yaml all match" + echo " the last run." exit 1 ;; esac @@ -46,43 +55,23 @@ else fi # ============================================ -# 1. Prisma Dart Client (backend/lib/generated/) +# 1-2. Backend codegen (Prisma client + freezed), hash-guarded # ============================================ +# Previously these were two stages that each began by deleting their own +# output — `rm -rf backend/lib/generated` and a `find … | xargs rm -f` over +# every *.freezed.dart. That ran on every invocation regardless of whether +# anything had changed, destroying build_runner's asset graph and forcing a +# cold rebuild of ~735k generated lines each time. +# +# ensure-generated.sh now owns both stages and skips them when schema.prisma, +# the connector/freezed versions and build.yaml are all unchanged. Pass +# --force to get the old wipe-and-rebuild behaviour. if [ "$RUN_PRISMA" = true ]; then echo "" - echo "=== [1/4] Prisma Dart Client ===" - echo "Deleting backend/lib/generated/..." - rm -rf backend/lib/generated - - echo "Running dart pub get in backend/..." - cd "$ROOT_DIR/backend" - dart pub get - - echo "Generating Prisma client from schema..." - dart run prisma_flutter_connector:generate \ - --schema prisma/schema.prisma \ - --output lib/generated \ - --server - - cd "$ROOT_DIR" - echo "Prisma client generated." - - # ============================================ - # 2. Backend CodeGen (freezed/json for generated models) - # ============================================ - echo "" - echo "=== [2/4] Backend CodeGen (freezed/json for Prisma models) ===" - echo "Deleting backend *.g.dart and *.freezed.dart files..." + echo "=== [1-2/4] Backend codegen (Prisma client + freezed) ===" cd "$ROOT_DIR/backend" - # Keep lib/generated/schema_registry.g.dart: it is emitted by the Prisma - # generator (step 1), not by build_runner, so deleting it here loses it - find lib \( -name "*.g.dart" ! -path "*/generated/schema_registry.g.dart" -o -name "*.freezed.dart" \) | xargs rm -f 2>/dev/null || true - - echo "Running build_runner in backend/..." - dart run build_runner build --delete-conflicting-outputs - + FORCE_CODEGEN="$FORCE" ./scripts/ensure-generated.sh cd "$ROOT_DIR" - echo "Backend codegen complete." fi # ============================================ @@ -108,8 +97,15 @@ fi if [ "$RUN_FLUTTER" = true ]; then echo "" echo "=== [4/4] Flutter CodeGen ===" - echo "Deleting *.g.dart and *.freezed.dart files..." - find lib -name "*.g.dart" -o -name "*.freezed.dart" | xargs rm -f 2>/dev/null || true + # Only wipe on --force. Deleting every generated file discards build_runner's + # asset graph, which turns an incremental rebuild (seconds) into a cold one + # (minutes) across freezed + json_serializable + riverpod_generator + envied. + # --delete-conflicting-outputs below already resolves the collisions this + # was guarding against. + if [ "$FORCE" = "1" ]; then + echo "Deleting *.g.dart and *.freezed.dart files (--force)..." + find lib -name "*.g.dart" -o -name "*.freezed.dart" | xargs rm -f 2>/dev/null || true + fi echo "Running flutter pub get..." flutter pub get diff --git a/scripts/use-db.sh b/scripts/use-db.sh new file mode 100755 index 0000000..32119a5 --- /dev/null +++ b/scripts/use-db.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +# Point the NATIVE backend (`dart_frog dev` on the host) at a database. +# +# source scripts/use-db.sh local # the docker compose Postgres +# source scripts/use-db.sh supabase # the shared cloud database +# ./scripts/use-db.sh # just show what is currently set +# +# MUST be sourced, not executed — it exports into your shell. Because +# backend/main.dart applies Platform.environment last, an exported DIRECT_URL +# beats anything in backend/.env, so this works without editing any file. +# +# In Docker you do not need this: docker-compose sets DIRECT_URL directly. +# To point the containers at Supabase instead, set FAM_DIRECT_URL. + +_use_db_target="${1:-}" + +# Host port for the compose Postgres. Keep in sync with FAM_PG_PORT in +# docker-compose.yml (5432 is usually taken by a Homebrew postgres). +_use_db_local="postgresql://familiarise:familiarise@localhost:${FAM_PG_PORT:-5433}/familiarise?sslmode=disable" + +case "$_use_db_target" in + local) + export DIRECT_URL="$_use_db_local" + export DATABASE_URL="$_use_db_local" + echo "DB target: LOCAL (localhost:${FAM_PG_PORT:-5433}/familiarise)" + ;; + + supabase) + # Read from backend/.env.supabase rather than storing credentials here. + _use_db_file="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)/backend/.env.supabase" + if [ ! -f "$_use_db_file" ]; then + echo "Missing $_use_db_file" >&2 + echo "Copy backend/.env.supabase.example to it and fill in the URLs." >&2 + return 1 2>/dev/null || exit 1 + fi + set -a + # shellcheck disable=SC1090 + . "$_use_db_file" + set +a + echo "DB target: SUPABASE (from backend/.env.supabase)" + ;; + + "") + if [ -n "${DIRECT_URL:-}" ]; then + # Strip credentials before printing. + _use_db_shown=${DIRECT_URL#*://} + echo "DB target: ${_use_db_shown##*@}" + else + echo "DB target: unset — backend/.env will decide." + echo "Run: source scripts/use-db.sh local" + fi + ;; + + *) + echo "Usage: source scripts/use-db.sh [local|supabase]" >&2 + return 1 2>/dev/null || exit 1 + ;; +esac + +# A DIRECT_URL left in backend/.env is harmless for precedence (the exported +# one wins) but is a trap for anyone reading the file, so say so once. +_use_db_envfile="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)/backend/.env" +if [ -n "$_use_db_target" ] && [ -f "$_use_db_envfile" ] \ + && grep -qE '^\s*(DIRECT_URL|DATABASE_URL)=' "$_use_db_envfile"; then + echo "note: backend/.env still defines DIRECT_URL/DATABASE_URL." >&2 + echo " The exported value above takes precedence, but consider" >&2 + echo " moving those lines to backend/.env.supabase to avoid confusion." >&2 +fi + +unset _use_db_target _use_db_local _use_db_file _use_db_shown _use_db_envfile From cabd7c62701942a487eef2c8cc3da727424e7d82 Mon Sep 17 00:00:00 2001 From: Kaustav Ghosh Date: Sat, 25 Jul 2026 00:21:22 +0530 Subject: [PATCH 2/4] fix(dev): apply dev seed per prompt block instead of one script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- backend/prisma/sql/seed-dev.sql | 689 ------------------ backend/prisma/sql/seed.d/01-auth.sql | 21 + backend/prisma/sql/seed.d/02-onboarding.sql | 28 + backend/prisma/sql/seed.d/03-profile.sql | 25 + backend/prisma/sql/seed.d/04-verification.sql | 25 + backend/prisma/sql/seed.d/05-plans.sql | 25 + backend/prisma/sql/seed.d/06-slots.sql | 25 + backend/prisma/sql/seed.d/07-explore.sql | 44 ++ backend/prisma/sql/seed.d/08-booking.sql | 43 ++ backend/prisma/sql/seed.d/09-checkout.sql | 47 ++ backend/prisma/sql/seed.d/10-trials.sql | 39 + backend/prisma/sql/seed.d/11-waitlist.sql | 44 ++ backend/prisma/sql/seed.d/12-documents.sql | 49 ++ backend/prisma/sql/seed.d/13-chat.sql | 22 + backend/prisma/sql/seed.d/14-referrals.sql | 26 + backend/prisma/sql/seed.d/15-reviews.sql | 45 ++ backend/prisma/sql/seed.d/16-support.sql | 26 + backend/prisma/sql/seed.d/17-feedback.sql | 21 + backend/prisma/sql/seed.d/18-payout.sql | 25 + backend/prisma/sql/seed.d/19-tax.sql | 25 + backend/prisma/sql/seed.d/20-staff.sql | 56 ++ .../prisma/sql/seed.d/21-announcements.sql | 26 + .../prisma/sql/seed.d/22-collaborations.sql | 42 ++ backend/prisma/sql/seed.d/23-dashboard.sql | 58 ++ docker/db-init/entrypoint.sh | 34 +- docs/getting-started/05-docker-local-dev.md | 25 +- scripts/gen-dev-seed.sh | 81 +- 27 files changed, 885 insertions(+), 731 deletions(-) delete mode 100644 backend/prisma/sql/seed-dev.sql create mode 100644 backend/prisma/sql/seed.d/01-auth.sql create mode 100644 backend/prisma/sql/seed.d/02-onboarding.sql create mode 100644 backend/prisma/sql/seed.d/03-profile.sql create mode 100644 backend/prisma/sql/seed.d/04-verification.sql create mode 100644 backend/prisma/sql/seed.d/05-plans.sql create mode 100644 backend/prisma/sql/seed.d/06-slots.sql create mode 100644 backend/prisma/sql/seed.d/07-explore.sql create mode 100644 backend/prisma/sql/seed.d/08-booking.sql create mode 100644 backend/prisma/sql/seed.d/09-checkout.sql create mode 100644 backend/prisma/sql/seed.d/10-trials.sql create mode 100644 backend/prisma/sql/seed.d/11-waitlist.sql create mode 100644 backend/prisma/sql/seed.d/12-documents.sql create mode 100644 backend/prisma/sql/seed.d/13-chat.sql create mode 100644 backend/prisma/sql/seed.d/14-referrals.sql create mode 100644 backend/prisma/sql/seed.d/15-reviews.sql create mode 100644 backend/prisma/sql/seed.d/16-support.sql create mode 100644 backend/prisma/sql/seed.d/17-feedback.sql create mode 100644 backend/prisma/sql/seed.d/18-payout.sql create mode 100644 backend/prisma/sql/seed.d/19-tax.sql create mode 100644 backend/prisma/sql/seed.d/20-staff.sql create mode 100644 backend/prisma/sql/seed.d/21-announcements.sql create mode 100644 backend/prisma/sql/seed.d/22-collaborations.sql create mode 100644 backend/prisma/sql/seed.d/23-dashboard.sql diff --git a/backend/prisma/sql/seed-dev.sql b/backend/prisma/sql/seed-dev.sql deleted file mode 100644 index b9a34dc..0000000 --- a/backend/prisma/sql/seed-dev.sql +++ /dev/null @@ -1,689 +0,0 @@ --- ───────────────────────────────────────────────────────────── --- Local development seed. --- --- HARVESTED from the `## Data Seeding` blocks of prompts/testing/unit/*.md, --- which are the fixtures the E2E/agent test prompts already assume exist. --- Regenerate with: ./scripts/gen-dev-seed.sh --- --- Each source file uses its own `test_unit__*` id prefix, so the blocks --- are self-contained and safe to concatenate in file order. --- --- NOT idempotent on its own — docker/db-init/entrypoint.sh guards it with a --- marker-row check so a re-run of `docker compose up` does not re-insert. --- ───────────────────────────────────────────────────────────── - -BEGIN; - --- ===== 01-auth.md ===== -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_auth_u1', 'Auth Unit User', 'test_unit_auth@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -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()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_auth_cp1', 'test_unit_auth_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_auth_cp1' WHERE id = 'test_unit_auth_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_auth_ck1', 'test_unit_auth_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_auth_np1', 'test_unit_auth_u1'); - --- ===== 02-onboarding.md ===== --- Fresh users with onboardingCompleted = false -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('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()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_onb_a1', 'test_unit_onb_cee', 'test_unit_onb_cee', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_onb_a2', 'test_unit_onb_cnt', 'test_unit_onb_cnt', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_onb_ck1', 'test_unit_onb_cee', true, NOW(), NOW()), - ('test_unit_onb_ck2', 'test_unit_onb_cnt', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_onb_np1', 'test_unit_onb_cee'), ('test_unit_onb_np2', 'test_unit_onb_cnt'); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_onb_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; -INSERT INTO "SubDomain" (id, name, "domainId", "createdAt", "updatedAt") -VALUES ('test_unit_onb_sub', 'Flutter Dev', 'test_unit_onb_dom', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - --- ===== 03-profile.md ===== --- Verified consultant user -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, phone, "createdAt", "updatedAt") -VALUES ('test_unit_profile_u1', 'Profile Test User', 'test_unit_profile@test.com', true, 'CONSULTANT', true, 'Original bio', 'Mumbai', '+919000000001', NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_profile_a1', 'test_unit_profile_u1', 'test_unit_profile_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_profile_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_profile_cp1', 'test_unit_profile_u1', 'test_unit_profile_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_profile_cp1' WHERE id = 'test_unit_profile_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_profile_ck1', 'test_unit_profile_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_profile_np1', 'test_unit_profile_u1'); - --- ===== 04-verification.md ===== --- Consultant with PENDING_VERIFICATION status -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_verif_u1', 'Verif Test User', 'test_unit_verif@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_verif_a1', 'test_unit_verif_u1', 'test_unit_verif_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_verif_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_verif_cp1', 'test_unit_verif_u1', 'test_unit_verif_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_verif_cp1' WHERE id = 'test_unit_verif_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_verif_ck1', 'test_unit_verif_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_verif_np1', 'test_unit_verif_u1'); - --- ===== 05-plans.md ===== --- Verified consultant -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_plans_u1', 'Plans Test Consultant', 'test_unit_plans@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_plans_a1', 'test_unit_plans_u1', 'test_unit_plans_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_plans_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_plans_cp1', 'test_unit_plans_u1', 'test_unit_plans_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_plans_cp1' WHERE id = 'test_unit_plans_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_plans_ck1', 'test_unit_plans_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_plans_np1', 'test_unit_plans_u1'); - --- ===== 06-slots.md ===== --- Verified consultant -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_slots_u1', 'Slots Test Consultant', 'test_unit_slots@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_slots_a1', 'test_unit_slots_u1', 'test_unit_slots_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_slots_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_slots_cp1', 'test_unit_slots_u1', 'test_unit_slots_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_slots_cp1' WHERE id = 'test_unit_slots_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_slots_ck1', 'test_unit_slots_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_slots_np1', 'test_unit_slots_u1'); - --- ===== 07-explore.md ===== --- Consultant with plan + reviews -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, "createdAt", "updatedAt") -VALUES - ('test_unit_explore_u1', 'Explore Consultant', 'test_unit_explore_cnt@test.com', true, 'CONSULTANT', true, 'Expert Flutter developer', 'Bangalore', NOW(), NOW()), - ('test_unit_explore_u2', 'Explore Consultee', 'test_unit_explore_cee@test.com', true, 'CONSULTEE', true, NULL, NULL, NOW(), NOW()); - -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()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_explore_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", headline, rating, "createdAt", "updatedAt") -VALUES ('test_unit_explore_cp1', 'test_unit_explore_u1', 'test_unit_explore_dom', 'WEEKLY', true, 'VERIFIED', 'Flutter Expert', 4.5, NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_explore_cp1' WHERE id = 'test_unit_explore_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_explore_cep1', 'test_unit_explore_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_explore_cep1' WHERE id = 'test_unit_explore_u2'; - --- Consultation plan -INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_explore_plan1', 'Flutter Consultation', 'Learn Flutter basics', 1, 50000, 'test_unit_explore_cp1', NOW(), NOW()); - --- Weekly availability slot -INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_explore_slot1', 'MONDAY', 600, 'MONDAY', 660, 330, 'test_unit_explore_cp1', NOW(), NOW()); - --- Review -INSERT INTO "ConsultantReview" (id, rating, "reviewDescription", "consultantProfileId", "consulteeProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_explore_rev1', 5, 'Excellent mentor, very helpful!', 'test_unit_explore_cp1', 'test_unit_explore_cep1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_explore_ck1', 'test_unit_explore_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_explore_np1', 'test_unit_explore_u2'); - --- ===== 08-booking.md ===== --- Consultant with plan + slots -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_booking_u1', 'Booking Consultant', 'test_unit_booking_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_booking_u2', 'Booking Consultee', 'test_unit_booking_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_booking_a1', 'test_unit_booking_u1', 'test_unit_booking_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_booking_a2', 'test_unit_booking_u2', 'test_unit_booking_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_booking_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_booking_cp1', 'test_unit_booking_u1', 'test_unit_booking_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_booking_cp1' WHERE id = 'test_unit_booking_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_booking_cep1', 'test_unit_booking_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_booking_cep1' WHERE id = 'test_unit_booking_u2'; - --- Consultation plan -INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_booking_plan1', 'Booking Test Plan', '1-hour consultation', 1, 50000, 'test_unit_booking_cp1', NOW(), NOW()); - --- Weekly availability slot (Monday 10:00-11:00 IST) -INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_booking_slot1', 'MONDAY', 270, 'MONDAY', 330, 330, 'test_unit_booking_cp1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_booking_ck1', 'test_unit_booking_u1', true, NOW(), NOW()), - ('test_unit_booking_ck2', 'test_unit_booking_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_booking_np1', 'test_unit_booking_u1'), ('test_unit_booking_np2', 'test_unit_booking_u2'); - --- ===== 09-checkout.md ===== --- Consultant + consultee + booking in APPROVED_PENDING_PAYMENT state -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_checkout_u1', 'Checkout Consultant', 'test_unit_checkout_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_checkout_u2', 'Checkout Consultee', 'test_unit_checkout_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_checkout_a1', 'test_unit_checkout_u1', 'test_unit_checkout_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_checkout_a2', 'test_unit_checkout_u2', 'test_unit_checkout_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_checkout_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_checkout_cp1', 'test_unit_checkout_u1', 'test_unit_checkout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_checkout_cp1' WHERE id = 'test_unit_checkout_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_checkout_cep1', 'test_unit_checkout_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_checkout_cep1' WHERE id = 'test_unit_checkout_u2'; - --- Consultation plan -INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_checkout_plan1', 'Checkout Test Plan', '1-hour session', 1, 75000, 'test_unit_checkout_cp1', NOW(), NOW()); - --- Consultation in APPROVED_PENDING_PAYMENT status -INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") -VALUES ('test_unit_checkout_con1', 'test_unit_checkout_plan1', 'APPROVED_PENDING_PAYMENT', 'test_unit_checkout_cep1', NOW(), NOW(), NOW()); - --- Appointment for the consultation -INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") -VALUES ('test_unit_checkout_apt1', 'CONSULTATION', 'test_unit_checkout_con1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_checkout_ck1', 'test_unit_checkout_u1', true, NOW(), NOW()), - ('test_unit_checkout_ck2', 'test_unit_checkout_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_checkout_np1', 'test_unit_checkout_u1'), ('test_unit_checkout_np2', 'test_unit_checkout_u2'); - --- ===== 10-trials.md ===== --- Consultant with subscription plan (freeTrialEnabled) + consultee -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_trials_u1', 'Trials Consultant', 'test_unit_trials_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_trials_u2', 'Trials Consultee', 'test_unit_trials_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_trials_a1', 'test_unit_trials_u1', 'test_unit_trials_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_trials_a2', 'test_unit_trials_u2', 'test_unit_trials_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_trials_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_trials_cp1', 'test_unit_trials_u1', 'test_unit_trials_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_trials_cp1' WHERE id = 'test_unit_trials_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_trials_cep1', 'test_unit_trials_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_trials_cep1' WHERE id = 'test_unit_trials_u2'; - --- Subscription plan with free trial enabled -INSERT INTO "SubscriptionPlan" (id, title, description, "durationInMonths", price, "callsPerWeek", "sessionDurationInHours", "freeTrialEnabled", "freeTrialDurationMinutes", "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_trials_sp1', 'Trial Subscription Plan', 'Monthly mentoring with free trial', 1, 200000, 1, 1, true, 30, 'test_unit_trials_cp1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_trials_ck1', 'test_unit_trials_u1', true, NOW(), NOW()), - ('test_unit_trials_ck2', 'test_unit_trials_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_trials_np1', 'test_unit_trials_u1'), ('test_unit_trials_np2', 'test_unit_trials_u2'); - --- ===== 11-waitlist.md ===== --- Consultant + consultee + full webinar (maxParticipants=1, 1 enrolled) -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_waitlist_u1', 'Waitlist Consultant', 'test_unit_waitlist_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_waitlist_u2', 'Waitlist Consultee', 'test_unit_waitlist_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()), - ('test_unit_waitlist_u3', 'Waitlist Enrolled', 'test_unit_waitlist_enr@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_waitlist_a1', 'test_unit_waitlist_u1', 'test_unit_waitlist_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_waitlist_a2', 'test_unit_waitlist_u2', 'test_unit_waitlist_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_waitlist_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_waitlist_cp1', 'test_unit_waitlist_u1', 'test_unit_waitlist_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_waitlist_cp1' WHERE id = 'test_unit_waitlist_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_waitlist_cep1', 'test_unit_waitlist_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_waitlist_cep1' WHERE id = 'test_unit_waitlist_u2'; - --- Webinar plan with maxParticipants=1 (full capacity) -INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_waitlist_wp1', 'Full Webinar', 'A sold-out webinar', 100000, 2, 1, 'test_unit_waitlist_cp1', NOW(), NOW()); - --- Webinar instance -INSERT INTO "Webinar" (id, status, "webinarPlanId", "createdAt", "updatedAt") -VALUES ('test_unit_waitlist_w1', 'SCHEDULED', 'test_unit_waitlist_wp1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_waitlist_ck1', 'test_unit_waitlist_u1', true, NOW(), NOW()), - ('test_unit_waitlist_ck2', 'test_unit_waitlist_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_waitlist_np1', 'test_unit_waitlist_u1'), ('test_unit_waitlist_np2', 'test_unit_waitlist_u2'); - --- ===== 12-documents.md ===== --- Consultant + consultee with an appointment -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_docs_u1', 'Docs Consultant', 'test_unit_docs_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_docs_u2', 'Docs Consultee', 'test_unit_docs_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_docs_a1', 'test_unit_docs_u1', 'test_unit_docs_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_docs_a2', 'test_unit_docs_u2', 'test_unit_docs_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_docs_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_docs_cp1', 'test_unit_docs_u1', 'test_unit_docs_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_docs_cp1' WHERE id = 'test_unit_docs_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_docs_cep1', 'test_unit_docs_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_docs_cep1' WHERE id = 'test_unit_docs_u2'; - --- Consultation plan + consultation + appointment -INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_docs_plan1', 'Docs Test Plan', 1, 50000, 'test_unit_docs_cp1', NOW(), NOW()); - -INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") -VALUES ('test_unit_docs_con1', 'test_unit_docs_plan1', 'SCHEDULED', 'test_unit_docs_cep1', NOW(), NOW(), NOW()); - -INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") -VALUES ('test_unit_docs_apt1', 'CONSULTATION', 'test_unit_docs_con1', NOW(), NOW()); - --- Seed an existing document -INSERT INTO "AppointmentDocument" (id, "fileName", "originalName", "fileSize", "mimeType", "fileUrl", "storagePath", description, "reviewStatus", "uploadedByRole", "appointmentId", "uploadedAt", "updatedAt") -VALUES ('test_unit_docs_doc1', 'resume.pdf', 'resume.pdf', 102400, 'application/pdf', 'https://example.com/resume.pdf', 'documents/test/resume.pdf', 'My resume for review', 'PENDING', 'CONSULTEE', 'test_unit_docs_apt1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_docs_ck1', 'test_unit_docs_u1', true, NOW(), NOW()), - ('test_unit_docs_ck2', 'test_unit_docs_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_docs_np1', 'test_unit_docs_u1'), ('test_unit_docs_np2', 'test_unit_docs_u2'); - --- ===== 13-chat.md ===== --- Simple user for chat UI test -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_chat_u1', 'Chat Test User', 'test_unit_chat@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_chat_a1', 'test_unit_chat_u1', 'test_unit_chat_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_chat_cep1', 'test_unit_chat_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_chat_cep1' WHERE id = 'test_unit_chat_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_chat_ck1', 'test_unit_chat_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_chat_np1', 'test_unit_chat_u1'); - --- ===== 14-referrals.md ===== --- User with referral code -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_referrals_u1', 'Referrals User', 'test_unit_referrals@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_referrals_a1', 'test_unit_referrals_u1', 'test_unit_referrals_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_referrals_cep1', 'test_unit_referrals_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_referrals_cep1' WHERE id = 'test_unit_referrals_u1'; - --- Pre-existing referral code -INSERT INTO "ReferralCode" (id, "userId", code, "referrerReward", "refereeReward", "isActive", "createdAt", "updatedAt") -VALUES ('test_unit_referrals_rc1', 'test_unit_referrals_u1', 'TESTREF123', 10000, 5000, true, NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_referrals_ck1', 'test_unit_referrals_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_referrals_np1', 'test_unit_referrals_u1'); - --- ===== 15-reviews.md ===== --- Consultant + consultee with completed appointment -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_reviews_u1', 'Reviews Consultant', 'test_unit_reviews_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_reviews_u2', 'Reviews Consultee', 'test_unit_reviews_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_reviews_a1', 'test_unit_reviews_u1', 'test_unit_reviews_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_reviews_a2', 'test_unit_reviews_u2', 'test_unit_reviews_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_reviews_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", rating, "createdAt", "updatedAt") -VALUES ('test_unit_reviews_cp1', 'test_unit_reviews_u1', 'test_unit_reviews_dom', 'WEEKLY', true, 'VERIFIED', 0, NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_reviews_cp1' WHERE id = 'test_unit_reviews_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_reviews_cep1', 'test_unit_reviews_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_reviews_cep1' WHERE id = 'test_unit_reviews_u2'; - --- Completed consultation + appointment -INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_reviews_plan1', 'Review Test Plan', 1, 50000, 'test_unit_reviews_cp1', NOW(), NOW()); - -INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") -VALUES ('test_unit_reviews_con1', 'test_unit_reviews_plan1', 'COMPLETED', 'test_unit_reviews_cep1', NOW(), NOW(), NOW()); - -INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") -VALUES ('test_unit_reviews_apt1', 'CONSULTATION', 'test_unit_reviews_con1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_reviews_ck1', 'test_unit_reviews_u1', true, NOW(), NOW()), - ('test_unit_reviews_ck2', 'test_unit_reviews_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_reviews_np1', 'test_unit_reviews_u1'), ('test_unit_reviews_np2', 'test_unit_reviews_u2'); - --- ===== 16-support.md ===== --- User with existing ticket -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_support_u1', 'Support User', 'test_unit_support@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_support_a1', 'test_unit_support_u1', 'test_unit_support_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_support_cep1', 'test_unit_support_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_support_cep1' WHERE id = 'test_unit_support_u1'; - --- Existing support ticket -INSERT INTO "support_tickets" (id, title, description, priority, status, category, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_support_t1', 'Payment not received', 'I completed payment but status still shows pending', 'HIGH', 'OPEN', 'PAYMENT_FAILED', 'test_unit_support_u1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_support_ck1', 'test_unit_support_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_support_np1', 'test_unit_support_u1'); - --- ===== 17-feedback.md ===== -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_feedback_u1', 'Feedback User', 'test_unit_feedback@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_feedback_a1', 'test_unit_feedback_u1', 'test_unit_feedback_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_feedback_cep1', 'test_unit_feedback_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_feedback_cep1' WHERE id = 'test_unit_feedback_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_feedback_ck1', 'test_unit_feedback_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_feedback_np1', 'test_unit_feedback_u1'); - --- ===== 18-payout.md ===== --- Verified consultant -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_payout_u1', 'Payout Consultant', 'test_unit_payout@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_payout_a1', 'test_unit_payout_u1', 'test_unit_payout_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_payout_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_payout_cp1', 'test_unit_payout_u1', 'test_unit_payout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_payout_cp1' WHERE id = 'test_unit_payout_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_payout_ck1', 'test_unit_payout_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_payout_np1', 'test_unit_payout_u1'); - --- ===== 19-tax.md ===== --- Verified consultant -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_tax_u1', 'Tax Consultant', 'test_unit_tax@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_tax_a1', 'test_unit_tax_u1', 'test_unit_tax_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_tax_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_tax_cp1', 'test_unit_tax_u1', 'test_unit_tax_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_tax_cp1' WHERE id = 'test_unit_tax_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_tax_ck1', 'test_unit_tax_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_tax_np1', 'test_unit_tax_u1'); - --- ===== 20-staff.md ===== --- Staff user -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_staff_u1', 'Staff User', 'test_unit_staff@test.com', true, 'STAFF', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_staff_a1', 'test_unit_staff_u1', 'test_unit_staff_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "StaffProfile" (id, "userId", department, position, "createdAt", "updatedAt") -VALUES ('test_unit_staff_sp1', 'test_unit_staff_u1', 'Support', 'Manager', NOW(), NOW()); -UPDATE "users" SET "staffProfileId" = 'test_unit_staff_sp1' WHERE id = 'test_unit_staff_u1'; - --- Consultant with pending verification -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_staff_u2', 'Pending Consultant', 'test_unit_staff_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_staff_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES ('test_unit_staff_cp1', 'test_unit_staff_u2', 'test_unit_staff_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_staff_cp1' WHERE id = 'test_unit_staff_u2'; - --- Pending verification request -INSERT INTO "ConsultantProfileVerification" (id, status, "consultantProfileId", "submittedAt", notes, "createdAt", "updatedAt") -VALUES ('test_unit_staff_pv1', 'PENDING', 'test_unit_staff_cp1', NOW(), 'Please verify my credentials', NOW(), NOW()); - --- User with open support ticket -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_staff_u3', 'Ticket User', 'test_unit_staff_tkt@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_staff_cep1', 'test_unit_staff_u3', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_staff_cep1' WHERE id = 'test_unit_staff_u3'; - -INSERT INTO "support_tickets" (id, title, description, priority, status, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_staff_t1', 'Staff Test Ticket', 'Need help with booking issue', 'HIGH', 'OPEN', 'test_unit_staff_u3', NOW(), NOW()); - --- Pending feedback -INSERT INTO "feedbacks" (id, title, description, rating, status, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_staff_fb1', 'Staff Test Feedback', 'Great platform but needs improvement', 4, 'PENDING', 'test_unit_staff_u3', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_staff_ck1', 'test_unit_staff_u1', true, NOW(), NOW()), - ('test_unit_staff_ck2', 'test_unit_staff_u2', true, NOW(), NOW()), - ('test_unit_staff_ck3', 'test_unit_staff_u3', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_staff_np1', 'test_unit_staff_u1'), ('test_unit_staff_np2', 'test_unit_staff_u2'), ('test_unit_staff_np3', 'test_unit_staff_u3'); - --- ===== 21-announcements.md ===== --- Active announcement -INSERT INTO "announcements" (id, title, content, "isActive", "startDate", "endDate", "backgroundColor", "textColor", "linkUrl", "linkText", "createdBy", "createdAt", "updatedAt") -VALUES ('test_unit_announce_1', 'Platform Update', 'We are rolling out new features this week! Check out the new booking flow.', true, NOW() - INTERVAL '1 day', NOW() + INTERVAL '7 days', '#1E40AF', '#FFFFFF', 'https://familiarise.com/updates', 'Learn More', 'system', NOW(), NOW()); - --- User to view the announcement -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES ('test_unit_announce_u1', 'Announce User', 'test_unit_announce@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES ('test_unit_announce_a1', 'test_unit_announce_u1', 'test_unit_announce_u1', 'credential', - '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_announce_cep1', 'test_unit_announce_u1', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_announce_cep1' WHERE id = 'test_unit_announce_u1'; - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES ('test_unit_announce_ck1', 'test_unit_announce_u1', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_announce_np1', 'test_unit_announce_u1'); - --- ===== 22-collaborations.md ===== --- Two consultants: one invites the other to collaborate on a webinar -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_collab_u1', 'Collab Host', 'test_unit_collab_host@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_collab_u2', 'Collab Invited', 'test_unit_collab_inv@test.com', true, 'CONSULTANT', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_collab_a1', 'test_unit_collab_u1', 'test_unit_collab_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_collab_a2', 'test_unit_collab_u2', 'test_unit_collab_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_collab_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") -VALUES - ('test_unit_collab_cp1', 'test_unit_collab_u1', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()), - ('test_unit_collab_cp2', 'test_unit_collab_u2', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp1' WHERE id = 'test_unit_collab_u1'; -UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp2' WHERE id = 'test_unit_collab_u2'; - --- Webinar plan owned by host -INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_collab_wp1', 'Collab Webinar', 'A joint webinar on Flutter', 100000, 2, 50, 'test_unit_collab_cp1', NOW(), NOW()); - --- Collaboration invitation (PENDING) -INSERT INTO "WebinarCollaborator" (id, "consultantProfileId", "webinarPlanId", role, "revenueSharePercentage", status, "invitedById", "createdAt", "updatedAt") -VALUES ('test_unit_collab_wc1', 'test_unit_collab_cp2', 'test_unit_collab_wp1', 'CO_HOST', 30.0, 'PENDING', 'test_unit_collab_cp1', NOW(), NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_collab_ck1', 'test_unit_collab_u1', true, NOW(), NOW()), - ('test_unit_collab_ck2', 'test_unit_collab_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_collab_np1', 'test_unit_collab_u1'), ('test_unit_collab_np2', 'test_unit_collab_u2'); - --- ===== 23-dashboard.md ===== --- Consultant with stats/earnings/pending requests -INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") -VALUES - ('test_unit_dash_u1', 'Dash Consultant', 'test_unit_dash_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), - ('test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); - -INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") -VALUES - ('test_unit_dash_a1', 'test_unit_dash_u1', 'test_unit_dash_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), - ('test_unit_dash_a2', 'test_unit_dash_u2', 'test_unit_dash_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); - -INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") -VALUES ('test_unit_dash_dom', 'Technology', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; - -INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "totalRevenue", "pendingRevenue", rating, "createdAt", "updatedAt") -VALUES ('test_unit_dash_cp1', 'test_unit_dash_u1', 'test_unit_dash_dom', 'WEEKLY', true, 'VERIFIED', 500000, 100000, 4.5, NOW(), NOW()); -UPDATE "users" SET "consultantProfileId" = 'test_unit_dash_cp1' WHERE id = 'test_unit_dash_u1'; - -INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") -VALUES ('test_unit_dash_cep1', 'test_unit_dash_u2', NOW(), NOW()); -UPDATE "users" SET "consulteeProfileId" = 'test_unit_dash_cep1' WHERE id = 'test_unit_dash_u2'; - --- Consultation plan -INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") -VALUES ('test_unit_dash_plan1', 'Dashboard Plan', 1, 50000, 'test_unit_dash_cp1', NOW(), NOW()); - --- Pending consultation (shows as pending request for consultant) -INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") -VALUES ('test_unit_dash_con1', 'test_unit_dash_plan1', 'PENDING', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); - --- Scheduled consultation (shows as upcoming for both) -INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") -VALUES ('test_unit_dash_con2', 'test_unit_dash_plan1', 'SCHEDULED', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); - -INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") -VALUES ('test_unit_dash_apt1', 'CONSULTATION', 'test_unit_dash_con2', NOW(), NOW()); - --- Upcoming slot -INSERT INTO "SlotOfAppointment" (id, "startsAt", "endsAt", "appointmentId", "createdAt", "updatedAt") -VALUES ('test_unit_dash_soa1', NOW() + INTERVAL '2 days', NOW() + INTERVAL '2 days' + INTERVAL '1 hour', 'test_unit_dash_apt1', NOW(), NOW()); - --- Activity log -INSERT INTO "ActivityLog" (id, "activityType", description, "actorId", "actorName", "consultantProfileId", "createdAt") -VALUES ('test_unit_dash_al1', 'CONSULTATION_BOOKED', 'Dash Consultee booked a consultation', 'test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cp1', NOW()); - -INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") -VALUES - ('test_unit_dash_ck1', 'test_unit_dash_u1', true, NOW(), NOW()), - ('test_unit_dash_ck2', 'test_unit_dash_u2', true, NOW(), NOW()); -INSERT INTO "notification_preferences" (id, "userId") -VALUES ('test_unit_dash_np1', 'test_unit_dash_u1'), ('test_unit_dash_np2', 'test_unit_dash_u2'); - -COMMIT; diff --git a/backend/prisma/sql/seed.d/01-auth.sql b/backend/prisma/sql/seed.d/01-auth.sql new file mode 100644 index 0000000..7522efc --- /dev/null +++ b/backend/prisma/sql/seed.d/01-auth.sql @@ -0,0 +1,21 @@ +-- Generated from prompts/testing/unit/01-auth.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_auth_u1', 'Auth Unit User', 'test_unit_auth@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +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()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_auth_cp1', 'test_unit_auth_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_auth_cp1' WHERE id = 'test_unit_auth_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_auth_ck1', 'test_unit_auth_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_auth_np1', 'test_unit_auth_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/02-onboarding.sql b/backend/prisma/sql/seed.d/02-onboarding.sql new file mode 100644 index 0000000..6bc6510 --- /dev/null +++ b/backend/prisma/sql/seed.d/02-onboarding.sql @@ -0,0 +1,28 @@ +-- Generated from prompts/testing/unit/02-onboarding.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Fresh users with onboardingCompleted = false +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('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()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_onb_a1', 'test_unit_onb_cee', 'test_unit_onb_cee', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_onb_a2', 'test_unit_onb_cnt', 'test_unit_onb_cnt', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_onb_ck1', 'test_unit_onb_cee', true, NOW(), NOW()), + ('test_unit_onb_ck2', 'test_unit_onb_cnt', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_onb_np1', 'test_unit_onb_cee'), ('test_unit_onb_np2', 'test_unit_onb_cnt'); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_onb_dom', 'Technology [test_unit_onb_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; +INSERT INTO "SubDomain" (id, name, "domainId", "createdAt", "updatedAt") +VALUES ('test_unit_onb_sub', 'Flutter Dev', 'test_unit_onb_dom', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +COMMIT; diff --git a/backend/prisma/sql/seed.d/03-profile.sql b/backend/prisma/sql/seed.d/03-profile.sql new file mode 100644 index 0000000..4c4e07d --- /dev/null +++ b/backend/prisma/sql/seed.d/03-profile.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/03-profile.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Verified consultant user +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, phone, "createdAt", "updatedAt") +VALUES ('test_unit_profile_u1', 'Profile Test User', 'test_unit_profile@test.com', true, 'CONSULTANT', true, 'Original bio', 'Mumbai', '+919000000001', NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_profile_a1', 'test_unit_profile_u1', 'test_unit_profile_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_profile_dom', 'Technology [test_unit_profile_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_profile_cp1', 'test_unit_profile_u1', 'test_unit_profile_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_profile_cp1' WHERE id = 'test_unit_profile_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_profile_ck1', 'test_unit_profile_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_profile_np1', 'test_unit_profile_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/04-verification.sql b/backend/prisma/sql/seed.d/04-verification.sql new file mode 100644 index 0000000..3fbbc95 --- /dev/null +++ b/backend/prisma/sql/seed.d/04-verification.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/04-verification.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant with PENDING_VERIFICATION status +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_verif_u1', 'Verif Test User', 'test_unit_verif@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_verif_a1', 'test_unit_verif_u1', 'test_unit_verif_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_verif_dom', 'Technology [test_unit_verif_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_verif_cp1', 'test_unit_verif_u1', 'test_unit_verif_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_verif_cp1' WHERE id = 'test_unit_verif_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_verif_ck1', 'test_unit_verif_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_verif_np1', 'test_unit_verif_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/05-plans.sql b/backend/prisma/sql/seed.d/05-plans.sql new file mode 100644 index 0000000..d83ff3d --- /dev/null +++ b/backend/prisma/sql/seed.d/05-plans.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/05-plans.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_plans_u1', 'Plans Test Consultant', 'test_unit_plans@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_plans_a1', 'test_unit_plans_u1', 'test_unit_plans_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_plans_dom', 'Technology [test_unit_plans_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_plans_cp1', 'test_unit_plans_u1', 'test_unit_plans_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_plans_cp1' WHERE id = 'test_unit_plans_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_plans_ck1', 'test_unit_plans_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_plans_np1', 'test_unit_plans_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/06-slots.sql b/backend/prisma/sql/seed.d/06-slots.sql new file mode 100644 index 0000000..9c257c4 --- /dev/null +++ b/backend/prisma/sql/seed.d/06-slots.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/06-slots.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_slots_u1', 'Slots Test Consultant', 'test_unit_slots@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_slots_a1', 'test_unit_slots_u1', 'test_unit_slots_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_slots_dom', 'Technology [test_unit_slots_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_slots_cp1', 'test_unit_slots_u1', 'test_unit_slots_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_slots_cp1' WHERE id = 'test_unit_slots_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_slots_ck1', 'test_unit_slots_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_slots_np1', 'test_unit_slots_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/07-explore.sql b/backend/prisma/sql/seed.d/07-explore.sql new file mode 100644 index 0000000..35ed43a --- /dev/null +++ b/backend/prisma/sql/seed.d/07-explore.sql @@ -0,0 +1,44 @@ +-- Generated from prompts/testing/unit/07-explore.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant with plan + reviews +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", bio, city, "createdAt", "updatedAt") +VALUES + ('test_unit_explore_u1', 'Explore Consultant', 'test_unit_explore_cnt@test.com', true, 'CONSULTANT', true, 'Expert Flutter developer', 'Bangalore', NOW(), NOW()), + ('test_unit_explore_u2', 'Explore Consultee', 'test_unit_explore_cee@test.com', true, 'CONSULTEE', true, NULL, NULL, NOW(), NOW()); + +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()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_explore_dom', 'Technology [test_unit_explore_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", headline, rating, "createdAt", "updatedAt") +VALUES ('test_unit_explore_cp1', 'test_unit_explore_u1', 'test_unit_explore_dom', 'WEEKLY', true, 'VERIFIED', 'Flutter Expert', 4.5, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_explore_cp1' WHERE id = 'test_unit_explore_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_cep1', 'test_unit_explore_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_explore_cep1' WHERE id = 'test_unit_explore_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_plan1', 'Flutter Consultation', 'Learn Flutter basics', 1, 50000, 'test_unit_explore_cp1', NOW(), NOW()); + +-- Weekly availability slot +INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_slot1', 'MONDAY', 600, 'MONDAY', 660, 330, 'test_unit_explore_cp1', NOW(), NOW()); + +-- Review +INSERT INTO "ConsultantReview" (id, rating, "reviewDescription", "consultantProfileId", "consulteeProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_explore_rev1', 5, 'Excellent mentor, very helpful!', 'test_unit_explore_cp1', 'test_unit_explore_cep1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_explore_ck1', 'test_unit_explore_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_explore_np1', 'test_unit_explore_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/08-booking.sql b/backend/prisma/sql/seed.d/08-booking.sql new file mode 100644 index 0000000..a1c9fcd --- /dev/null +++ b/backend/prisma/sql/seed.d/08-booking.sql @@ -0,0 +1,43 @@ +-- Generated from prompts/testing/unit/08-booking.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant with plan + slots +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_booking_u1', 'Booking Consultant', 'test_unit_booking_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_booking_u2', 'Booking Consultee', 'test_unit_booking_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_booking_a1', 'test_unit_booking_u1', 'test_unit_booking_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_booking_a2', 'test_unit_booking_u2', 'test_unit_booking_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_booking_dom', 'Technology [test_unit_booking_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_booking_cp1', 'test_unit_booking_u1', 'test_unit_booking_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_booking_cp1' WHERE id = 'test_unit_booking_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_cep1', 'test_unit_booking_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_booking_cep1' WHERE id = 'test_unit_booking_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_plan1', 'Booking Test Plan', '1-hour consultation', 1, 50000, 'test_unit_booking_cp1', NOW(), NOW()); + +-- Weekly availability slot (Monday 10:00-11:00 IST) +INSERT INTO "SlotOfAvailabilityWeekly" (id, "startDay", "startTimeUtc", "endDay", "endTimeUtc", "utcOffsetMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_booking_slot1', 'MONDAY', 270, 'MONDAY', 330, 330, 'test_unit_booking_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_booking_ck1', 'test_unit_booking_u1', true, NOW(), NOW()), + ('test_unit_booking_ck2', 'test_unit_booking_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_booking_np1', 'test_unit_booking_u1'), ('test_unit_booking_np2', 'test_unit_booking_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/09-checkout.sql b/backend/prisma/sql/seed.d/09-checkout.sql new file mode 100644 index 0000000..47d4965 --- /dev/null +++ b/backend/prisma/sql/seed.d/09-checkout.sql @@ -0,0 +1,47 @@ +-- Generated from prompts/testing/unit/09-checkout.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant + consultee + booking in APPROVED_PENDING_PAYMENT state +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_checkout_u1', 'Checkout Consultant', 'test_unit_checkout_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_checkout_u2', 'Checkout Consultee', 'test_unit_checkout_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_checkout_a1', 'test_unit_checkout_u1', 'test_unit_checkout_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_checkout_a2', 'test_unit_checkout_u2', 'test_unit_checkout_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_checkout_dom', 'Technology [test_unit_checkout_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_cp1', 'test_unit_checkout_u1', 'test_unit_checkout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_checkout_cp1' WHERE id = 'test_unit_checkout_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_cep1', 'test_unit_checkout_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_checkout_cep1' WHERE id = 'test_unit_checkout_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, description, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_plan1', 'Checkout Test Plan', '1-hour session', 1, 75000, 'test_unit_checkout_cp1', NOW(), NOW()); + +-- Consultation in APPROVED_PENDING_PAYMENT status +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_con1', 'test_unit_checkout_plan1', 'APPROVED_PENDING_PAYMENT', 'test_unit_checkout_cep1', NOW(), NOW(), NOW()); + +-- Appointment for the consultation +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_checkout_apt1', 'CONSULTATION', 'test_unit_checkout_con1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_checkout_ck1', 'test_unit_checkout_u1', true, NOW(), NOW()), + ('test_unit_checkout_ck2', 'test_unit_checkout_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_checkout_np1', 'test_unit_checkout_u1'), ('test_unit_checkout_np2', 'test_unit_checkout_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/10-trials.sql b/backend/prisma/sql/seed.d/10-trials.sql new file mode 100644 index 0000000..a1ecf92 --- /dev/null +++ b/backend/prisma/sql/seed.d/10-trials.sql @@ -0,0 +1,39 @@ +-- Generated from prompts/testing/unit/10-trials.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant with subscription plan (freeTrialEnabled) + consultee +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_trials_u1', 'Trials Consultant', 'test_unit_trials_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_trials_u2', 'Trials Consultee', 'test_unit_trials_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_trials_a1', 'test_unit_trials_u1', 'test_unit_trials_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_trials_a2', 'test_unit_trials_u2', 'test_unit_trials_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_trials_dom', 'Technology [test_unit_trials_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_trials_cp1', 'test_unit_trials_u1', 'test_unit_trials_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_trials_cp1' WHERE id = 'test_unit_trials_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_trials_cep1', 'test_unit_trials_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_trials_cep1' WHERE id = 'test_unit_trials_u2'; + +-- Subscription plan with free trial enabled +INSERT INTO "SubscriptionPlan" (id, title, description, "durationInMonths", price, "callsPerWeek", "sessionDurationInHours", "freeTrialEnabled", "freeTrialDurationMinutes", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_trials_sp1', 'Trial Subscription Plan', 'Monthly mentoring with free trial', 1, 200000, 1, 1, true, 30, 'test_unit_trials_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_trials_ck1', 'test_unit_trials_u1', true, NOW(), NOW()), + ('test_unit_trials_ck2', 'test_unit_trials_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_trials_np1', 'test_unit_trials_u1'), ('test_unit_trials_np2', 'test_unit_trials_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/11-waitlist.sql b/backend/prisma/sql/seed.d/11-waitlist.sql new file mode 100644 index 0000000..f087176 --- /dev/null +++ b/backend/prisma/sql/seed.d/11-waitlist.sql @@ -0,0 +1,44 @@ +-- Generated from prompts/testing/unit/11-waitlist.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant + consultee + full webinar (maxParticipants=1, 1 enrolled) +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_waitlist_u1', 'Waitlist Consultant', 'test_unit_waitlist_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_waitlist_u2', 'Waitlist Consultee', 'test_unit_waitlist_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()), + ('test_unit_waitlist_u3', 'Waitlist Enrolled', 'test_unit_waitlist_enr@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_waitlist_a1', 'test_unit_waitlist_u1', 'test_unit_waitlist_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_waitlist_a2', 'test_unit_waitlist_u2', 'test_unit_waitlist_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_dom', 'Technology [test_unit_waitlist_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_cp1', 'test_unit_waitlist_u1', 'test_unit_waitlist_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_waitlist_cp1' WHERE id = 'test_unit_waitlist_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_cep1', 'test_unit_waitlist_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_waitlist_cep1' WHERE id = 'test_unit_waitlist_u2'; + +-- Webinar plan with maxParticipants=1 (full capacity) +INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_wp1', 'Full Webinar', 'A sold-out webinar', 100000, 2, 1, 'test_unit_waitlist_cp1', NOW(), NOW()); + +-- Webinar instance +INSERT INTO "Webinar" (id, status, "webinarPlanId", "createdAt", "updatedAt") +VALUES ('test_unit_waitlist_w1', 'SCHEDULED', 'test_unit_waitlist_wp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_waitlist_ck1', 'test_unit_waitlist_u1', true, NOW(), NOW()), + ('test_unit_waitlist_ck2', 'test_unit_waitlist_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_waitlist_np1', 'test_unit_waitlist_u1'), ('test_unit_waitlist_np2', 'test_unit_waitlist_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/12-documents.sql b/backend/prisma/sql/seed.d/12-documents.sql new file mode 100644 index 0000000..456a906 --- /dev/null +++ b/backend/prisma/sql/seed.d/12-documents.sql @@ -0,0 +1,49 @@ +-- Generated from prompts/testing/unit/12-documents.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant + consultee with an appointment +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_docs_u1', 'Docs Consultant', 'test_unit_docs_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_docs_u2', 'Docs Consultee', 'test_unit_docs_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_docs_a1', 'test_unit_docs_u1', 'test_unit_docs_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_docs_a2', 'test_unit_docs_u2', 'test_unit_docs_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_docs_dom', 'Technology [test_unit_docs_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_docs_cp1', 'test_unit_docs_u1', 'test_unit_docs_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_docs_cp1' WHERE id = 'test_unit_docs_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_cep1', 'test_unit_docs_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_docs_cep1' WHERE id = 'test_unit_docs_u2'; + +-- Consultation plan + consultation + appointment +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_plan1', 'Docs Test Plan', 1, 50000, 'test_unit_docs_cp1', NOW(), NOW()); + +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_docs_con1', 'test_unit_docs_plan1', 'SCHEDULED', 'test_unit_docs_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_docs_apt1', 'CONSULTATION', 'test_unit_docs_con1', NOW(), NOW()); + +-- Seed an existing document +INSERT INTO "AppointmentDocument" (id, "fileName", "originalName", "fileSize", "mimeType", "fileUrl", "storagePath", description, "reviewStatus", "uploadedByRole", "appointmentId", "uploadedAt", "updatedAt") +VALUES ('test_unit_docs_doc1', 'resume.pdf', 'resume.pdf', 102400, 'application/pdf', 'https://example.com/resume.pdf', 'documents/test/resume.pdf', 'My resume for review', 'PENDING', 'CONSULTEE', 'test_unit_docs_apt1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_docs_ck1', 'test_unit_docs_u1', true, NOW(), NOW()), + ('test_unit_docs_ck2', 'test_unit_docs_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_docs_np1', 'test_unit_docs_u1'), ('test_unit_docs_np2', 'test_unit_docs_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/13-chat.sql b/backend/prisma/sql/seed.d/13-chat.sql new file mode 100644 index 0000000..c662c1d --- /dev/null +++ b/backend/prisma/sql/seed.d/13-chat.sql @@ -0,0 +1,22 @@ +-- Generated from prompts/testing/unit/13-chat.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Simple user for chat UI test +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_chat_u1', 'Chat Test User', 'test_unit_chat@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_chat_a1', 'test_unit_chat_u1', 'test_unit_chat_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_chat_cep1', 'test_unit_chat_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_chat_cep1' WHERE id = 'test_unit_chat_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_chat_ck1', 'test_unit_chat_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_chat_np1', 'test_unit_chat_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/14-referrals.sql b/backend/prisma/sql/seed.d/14-referrals.sql new file mode 100644 index 0000000..ef7186d --- /dev/null +++ b/backend/prisma/sql/seed.d/14-referrals.sql @@ -0,0 +1,26 @@ +-- Generated from prompts/testing/unit/14-referrals.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- User with referral code +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_u1', 'Referrals User', 'test_unit_referrals@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_referrals_a1', 'test_unit_referrals_u1', 'test_unit_referrals_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_cep1', 'test_unit_referrals_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_referrals_cep1' WHERE id = 'test_unit_referrals_u1'; + +-- Pre-existing referral code +INSERT INTO "ReferralCode" (id, "userId", code, "referrerReward", "refereeReward", "isActive", "createdAt", "updatedAt") +VALUES ('test_unit_referrals_rc1', 'test_unit_referrals_u1', 'TESTREF123', 10000, 5000, true, NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_referrals_ck1', 'test_unit_referrals_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_referrals_np1', 'test_unit_referrals_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/15-reviews.sql b/backend/prisma/sql/seed.d/15-reviews.sql new file mode 100644 index 0000000..4acf03e --- /dev/null +++ b/backend/prisma/sql/seed.d/15-reviews.sql @@ -0,0 +1,45 @@ +-- Generated from prompts/testing/unit/15-reviews.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant + consultee with completed appointment +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_reviews_u1', 'Reviews Consultant', 'test_unit_reviews_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_reviews_u2', 'Reviews Consultee', 'test_unit_reviews_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_reviews_a1', 'test_unit_reviews_u1', 'test_unit_reviews_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_reviews_a2', 'test_unit_reviews_u2', 'test_unit_reviews_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_reviews_dom', 'Technology [test_unit_reviews_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", rating, "createdAt", "updatedAt") +VALUES ('test_unit_reviews_cp1', 'test_unit_reviews_u1', 'test_unit_reviews_dom', 'WEEKLY', true, 'VERIFIED', 0, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_reviews_cp1' WHERE id = 'test_unit_reviews_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_cep1', 'test_unit_reviews_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_reviews_cep1' WHERE id = 'test_unit_reviews_u2'; + +-- Completed consultation + appointment +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_plan1', 'Review Test Plan', 1, 50000, 'test_unit_reviews_cp1', NOW(), NOW()); + +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_con1', 'test_unit_reviews_plan1', 'COMPLETED', 'test_unit_reviews_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_reviews_apt1', 'CONSULTATION', 'test_unit_reviews_con1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_reviews_ck1', 'test_unit_reviews_u1', true, NOW(), NOW()), + ('test_unit_reviews_ck2', 'test_unit_reviews_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_reviews_np1', 'test_unit_reviews_u1'), ('test_unit_reviews_np2', 'test_unit_reviews_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/16-support.sql b/backend/prisma/sql/seed.d/16-support.sql new file mode 100644 index 0000000..904be1f --- /dev/null +++ b/backend/prisma/sql/seed.d/16-support.sql @@ -0,0 +1,26 @@ +-- Generated from prompts/testing/unit/16-support.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- User with existing ticket +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_support_u1', 'Support User', 'test_unit_support@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_support_a1', 'test_unit_support_u1', 'test_unit_support_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_support_cep1', 'test_unit_support_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_support_cep1' WHERE id = 'test_unit_support_u1'; + +-- Existing support ticket +INSERT INTO "support_tickets" (id, title, description, priority, status, category, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_support_t1', 'Payment not received', 'I completed payment but status still shows pending', 'HIGH', 'OPEN', 'PAYMENT_FAILED', 'test_unit_support_u1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_support_ck1', 'test_unit_support_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_support_np1', 'test_unit_support_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/17-feedback.sql b/backend/prisma/sql/seed.d/17-feedback.sql new file mode 100644 index 0000000..f38aa16 --- /dev/null +++ b/backend/prisma/sql/seed.d/17-feedback.sql @@ -0,0 +1,21 @@ +-- Generated from prompts/testing/unit/17-feedback.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_feedback_u1', 'Feedback User', 'test_unit_feedback@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_feedback_a1', 'test_unit_feedback_u1', 'test_unit_feedback_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_feedback_cep1', 'test_unit_feedback_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_feedback_cep1' WHERE id = 'test_unit_feedback_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_feedback_ck1', 'test_unit_feedback_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_feedback_np1', 'test_unit_feedback_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/18-payout.sql b/backend/prisma/sql/seed.d/18-payout.sql new file mode 100644 index 0000000..8626c3c --- /dev/null +++ b/backend/prisma/sql/seed.d/18-payout.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/18-payout.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_payout_u1', 'Payout Consultant', 'test_unit_payout@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_payout_a1', 'test_unit_payout_u1', 'test_unit_payout_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_payout_dom', 'Technology [test_unit_payout_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_payout_cp1', 'test_unit_payout_u1', 'test_unit_payout_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_payout_cp1' WHERE id = 'test_unit_payout_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_payout_ck1', 'test_unit_payout_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_payout_np1', 'test_unit_payout_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/19-tax.sql b/backend/prisma/sql/seed.d/19-tax.sql new file mode 100644 index 0000000..bbc45c6 --- /dev/null +++ b/backend/prisma/sql/seed.d/19-tax.sql @@ -0,0 +1,25 @@ +-- Generated from prompts/testing/unit/19-tax.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Verified consultant +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_tax_u1', 'Tax Consultant', 'test_unit_tax@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_tax_a1', 'test_unit_tax_u1', 'test_unit_tax_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_tax_dom', 'Technology [test_unit_tax_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_tax_cp1', 'test_unit_tax_u1', 'test_unit_tax_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_tax_cp1' WHERE id = 'test_unit_tax_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_tax_ck1', 'test_unit_tax_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_tax_np1', 'test_unit_tax_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/20-staff.sql b/backend/prisma/sql/seed.d/20-staff.sql new file mode 100644 index 0000000..7600fdc --- /dev/null +++ b/backend/prisma/sql/seed.d/20-staff.sql @@ -0,0 +1,56 @@ +-- Generated from prompts/testing/unit/20-staff.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Staff user +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u1', 'Staff User', 'test_unit_staff@test.com', true, 'STAFF', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_staff_a1', 'test_unit_staff_u1', 'test_unit_staff_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "StaffProfile" (id, "userId", department, position, "createdAt", "updatedAt") +VALUES ('test_unit_staff_sp1', 'test_unit_staff_u1', 'Support', 'Manager', NOW(), NOW()); +UPDATE "users" SET "staffProfileId" = 'test_unit_staff_sp1' WHERE id = 'test_unit_staff_u1'; + +-- Consultant with pending verification +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u2', 'Pending Consultant', 'test_unit_staff_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_staff_dom', 'Technology [test_unit_staff_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES ('test_unit_staff_cp1', 'test_unit_staff_u2', 'test_unit_staff_dom', 'WEEKLY', false, 'PENDING_VERIFICATION', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_staff_cp1' WHERE id = 'test_unit_staff_u2'; + +-- Pending verification request +INSERT INTO "ConsultantProfileVerification" (id, status, "consultantProfileId", "submittedAt", notes, "createdAt", "updatedAt") +VALUES ('test_unit_staff_pv1', 'PENDING', 'test_unit_staff_cp1', NOW(), 'Please verify my credentials', NOW(), NOW()); + +-- User with open support ticket +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_staff_u3', 'Ticket User', 'test_unit_staff_tkt@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_cep1', 'test_unit_staff_u3', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_staff_cep1' WHERE id = 'test_unit_staff_u3'; + +INSERT INTO "support_tickets" (id, title, description, priority, status, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_t1', 'Staff Test Ticket', 'Need help with booking issue', 'HIGH', 'OPEN', 'test_unit_staff_u3', NOW(), NOW()); + +-- Pending feedback +INSERT INTO "feedbacks" (id, title, description, rating, status, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_staff_fb1', 'Staff Test Feedback', 'Great platform but needs improvement', 4, 'PENDING', 'test_unit_staff_u3', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_staff_ck1', 'test_unit_staff_u1', true, NOW(), NOW()), + ('test_unit_staff_ck2', 'test_unit_staff_u2', true, NOW(), NOW()), + ('test_unit_staff_ck3', 'test_unit_staff_u3', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_staff_np1', 'test_unit_staff_u1'), ('test_unit_staff_np2', 'test_unit_staff_u2'), ('test_unit_staff_np3', 'test_unit_staff_u3'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/21-announcements.sql b/backend/prisma/sql/seed.d/21-announcements.sql new file mode 100644 index 0000000..aad08eb --- /dev/null +++ b/backend/prisma/sql/seed.d/21-announcements.sql @@ -0,0 +1,26 @@ +-- Generated from prompts/testing/unit/21-announcements.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Active announcement +INSERT INTO "announcements" (id, title, content, "isActive", "startDate", "endDate", "backgroundColor", "textColor", "linkUrl", "linkText", "createdBy", "createdAt", "updatedAt") +VALUES ('test_unit_announce_1', 'Platform Update', 'We are rolling out new features this week! Check out the new booking flow.', true, NOW() - INTERVAL '1 day', NOW() + INTERVAL '7 days', '#1E40AF', '#FFFFFF', 'https://familiarise.com/updates', 'Learn More', 'system', NOW(), NOW()); + +-- User to view the announcement +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES ('test_unit_announce_u1', 'Announce User', 'test_unit_announce@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES ('test_unit_announce_a1', 'test_unit_announce_u1', 'test_unit_announce_u1', 'credential', + '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_announce_cep1', 'test_unit_announce_u1', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_announce_cep1' WHERE id = 'test_unit_announce_u1'; + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES ('test_unit_announce_ck1', 'test_unit_announce_u1', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") VALUES ('test_unit_announce_np1', 'test_unit_announce_u1'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/22-collaborations.sql b/backend/prisma/sql/seed.d/22-collaborations.sql new file mode 100644 index 0000000..da71d2c --- /dev/null +++ b/backend/prisma/sql/seed.d/22-collaborations.sql @@ -0,0 +1,42 @@ +-- Generated from prompts/testing/unit/22-collaborations.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Two consultants: one invites the other to collaborate on a webinar +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_collab_u1', 'Collab Host', 'test_unit_collab_host@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_collab_u2', 'Collab Invited', 'test_unit_collab_inv@test.com', true, 'CONSULTANT', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_collab_a1', 'test_unit_collab_u1', 'test_unit_collab_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_collab_a2', 'test_unit_collab_u2', 'test_unit_collab_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_collab_dom', 'Technology [test_unit_collab_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "createdAt", "updatedAt") +VALUES + ('test_unit_collab_cp1', 'test_unit_collab_u1', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()), + ('test_unit_collab_cp2', 'test_unit_collab_u2', 'test_unit_collab_dom', 'WEEKLY', true, 'VERIFIED', NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp1' WHERE id = 'test_unit_collab_u1'; +UPDATE "users" SET "consultantProfileId" = 'test_unit_collab_cp2' WHERE id = 'test_unit_collab_u2'; + +-- Webinar plan owned by host +INSERT INTO "WebinarPlan" (id, title, description, price, "durationInHours", "maxParticipants", "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_collab_wp1', 'Collab Webinar', 'A joint webinar on Flutter', 100000, 2, 50, 'test_unit_collab_cp1', NOW(), NOW()); + +-- Collaboration invitation (PENDING) +INSERT INTO "WebinarCollaborator" (id, "consultantProfileId", "webinarPlanId", role, "revenueSharePercentage", status, "invitedById", "createdAt", "updatedAt") +VALUES ('test_unit_collab_wc1', 'test_unit_collab_cp2', 'test_unit_collab_wp1', 'CO_HOST', 30.0, 'PENDING', 'test_unit_collab_cp1', NOW(), NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_collab_ck1', 'test_unit_collab_u1', true, NOW(), NOW()), + ('test_unit_collab_ck2', 'test_unit_collab_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_collab_np1', 'test_unit_collab_u1'), ('test_unit_collab_np2', 'test_unit_collab_u2'); + +COMMIT; diff --git a/backend/prisma/sql/seed.d/23-dashboard.sql b/backend/prisma/sql/seed.d/23-dashboard.sql new file mode 100644 index 0000000..ee27bd2 --- /dev/null +++ b/backend/prisma/sql/seed.d/23-dashboard.sql @@ -0,0 +1,58 @@ +-- Generated from prompts/testing/unit/23-dashboard.md by scripts/gen-dev-seed.sh. +-- Do not edit directly; edit the prompt and regenerate. + +BEGIN; + +-- Consultant with stats/earnings/pending requests +INSERT INTO "users" (id, name, email, "emailVerified", role, "onboardingCompleted", "createdAt", "updatedAt") +VALUES + ('test_unit_dash_u1', 'Dash Consultant', 'test_unit_dash_cnt@test.com', true, 'CONSULTANT', true, NOW(), NOW()), + ('test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cee@test.com', true, 'CONSULTEE', true, NOW(), NOW()); + +INSERT INTO "accounts" (id, "userId", "accountId", "providerId", password, "createdAt", "updatedAt") +VALUES + ('test_unit_dash_a1', 'test_unit_dash_u1', 'test_unit_dash_u1', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()), + ('test_unit_dash_a2', 'test_unit_dash_u2', 'test_unit_dash_u2', 'credential', '$2a$12$LJ3m4ys3Lf.GEHPmwH8Xh.q5Y6oN5K6YKD3lVz8mG0V5Z8Z8Z8Z', NOW(), NOW()); + +INSERT INTO "Domain" (id, name, "createdAt", "updatedAt") +VALUES ('test_unit_dash_dom', 'Technology [test_unit_dash_dom]', NOW(), NOW()) ON CONFLICT (id) DO NOTHING; + +INSERT INTO "ConsultantProfile" (id, "userId", "domainId", "scheduleType", "isVerified", "verificationStatus", "totalRevenue", "pendingRevenue", rating, "createdAt", "updatedAt") +VALUES ('test_unit_dash_cp1', 'test_unit_dash_u1', 'test_unit_dash_dom', 'WEEKLY', true, 'VERIFIED', 500000, 100000, 4.5, NOW(), NOW()); +UPDATE "users" SET "consultantProfileId" = 'test_unit_dash_cp1' WHERE id = 'test_unit_dash_u1'; + +INSERT INTO "ConsulteeProfile" (id, "userId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_cep1', 'test_unit_dash_u2', NOW(), NOW()); +UPDATE "users" SET "consulteeProfileId" = 'test_unit_dash_cep1' WHERE id = 'test_unit_dash_u2'; + +-- Consultation plan +INSERT INTO "ConsultationPlan" (id, title, "durationInHours", price, "consultantProfileId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_plan1', 'Dashboard Plan', 1, 50000, 'test_unit_dash_cp1', NOW(), NOW()); + +-- Pending consultation (shows as pending request for consultant) +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_dash_con1', 'test_unit_dash_plan1', 'PENDING', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); + +-- Scheduled consultation (shows as upcoming for both) +INSERT INTO "Consultation" (id, "consultationPlanId", "requestStatus", "requestedById", "requestedAt", "createdAt", "updatedAt") +VALUES ('test_unit_dash_con2', 'test_unit_dash_plan1', 'SCHEDULED', 'test_unit_dash_cep1', NOW(), NOW(), NOW()); + +INSERT INTO "Appointment" (id, "appointmentType", "consultationId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_apt1', 'CONSULTATION', 'test_unit_dash_con2', NOW(), NOW()); + +-- Upcoming slot +INSERT INTO "SlotOfAppointment" (id, "startsAt", "endsAt", "appointmentId", "createdAt", "updatedAt") +VALUES ('test_unit_dash_soa1', NOW() + INTERVAL '2 days', NOW() + INTERVAL '2 days' + INTERVAL '1 hour', 'test_unit_dash_apt1', NOW(), NOW()); + +-- Activity log +INSERT INTO "ActivityLog" (id, "activityType", description, "actorId", "actorName", "consultantProfileId", "createdAt") +VALUES ('test_unit_dash_al1', 'CONSULTATION_BOOKED', 'Dash Consultee booked a consultation', 'test_unit_dash_u2', 'Dash Consultee', 'test_unit_dash_cp1', NOW()); + +INSERT INTO "cookie_preferences" (id, "userId", essential, "consentGivenAt", "consentUpdatedAt") +VALUES + ('test_unit_dash_ck1', 'test_unit_dash_u1', true, NOW(), NOW()), + ('test_unit_dash_ck2', 'test_unit_dash_u2', true, NOW(), NOW()); +INSERT INTO "notification_preferences" (id, "userId") +VALUES ('test_unit_dash_np1', 'test_unit_dash_u1'), ('test_unit_dash_np2', 'test_unit_dash_u2'); + +COMMIT; diff --git a/docker/db-init/entrypoint.sh b/docker/db-init/entrypoint.sh index 33b975d..9d9189b 100755 --- a/docker/db-init/entrypoint.sh +++ b/docker/db-init/entrypoint.sh @@ -55,17 +55,39 @@ psql "$DIRECT_URL" -v ON_ERROR_STOP=1 -q \ -f prisma/sql/check-constraints.sql # ── 3. Seed ──────────────────────────────────────────────────── -# seed-dev.sql is a plain concatenation of the prompt fixtures and is not -# itself idempotent, so gate it on a marker row from the first block. +# One file per test prompt, each applied in its own transaction. The prompts +# were written to run standalone against the shared database, so a few of them +# reference tables this schema no longer has. Applying them independently means +# that drift costs you one block instead of the whole seed — and gets reported +# rather than silently swallowed. +# +# The blocks are not individually idempotent, so gate the whole step on a +# marker row from the first one. echo "==> [3/3] dev seed" SEEDED=$(psql "$DIRECT_URL" -tAc \ "SELECT count(*) FROM \"users\" WHERE id = 'test_unit_auth_u1'") -if [ "$SEEDED" = "0" ]; then - psql "$DIRECT_URL" -v ON_ERROR_STOP=1 -q -f prisma/sql/seed-dev.sql - echo " seeded" -else +if [ "$SEEDED" != "0" ]; then echo " already seeded, skipping" +else + ok=0 + skipped="" + for f in prisma/sql/seed.d/*.sql; do + if err=$(psql "$DIRECT_URL" -v ON_ERROR_STOP=1 -q -f "$f" 2>&1); then + ok=$((ok + 1)) + else + skipped="$skipped $(basename "$f" .sql)" + # First ERROR line is the useful one; the rest is context. + echo " ! $(basename "$f"): $(echo "$err" | grep -m1 ERROR || echo "$err" | head -1)" + fi + done + + echo " seeded $ok/$(ls prisma/sql/seed.d/*.sql | wc -l | tr -d ' ') blocks" + if [ -n "$skipped" ]; then + echo " skipped:$skipped" + echo " (these prompts reference tables this schema does not have —" + echo " prompt drift, not a provisioning failure)" + fi fi TABLES=$(psql "$DIRECT_URL" -tAc \ diff --git a/docs/getting-started/05-docker-local-dev.md b/docs/getting-started/05-docker-local-dev.md index 14c2c4e..67a8d2f 100644 --- a/docs/getting-started/05-docker-local-dev.md +++ b/docs/getting-started/05-docker-local-dev.md @@ -90,18 +90,33 @@ to run `prisma db push --accept-data-loss` against production. 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` (129 models, - 106 enums) using Prisma 7.7.0, matching `familiarise_web`. +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. 2. `backend/prisma/sql/ledger-triggers.sql` and `check-constraints.sql` — vendored from `familiarise_web`, because `db push` does not manage triggers or CHECK constraints. Both are idempotent. -3. `backend/prisma/sql/seed-dev.sql` — the fixtures the test prompts already - assume, harvested from the `## Data Seeding` blocks in - `prompts/testing/unit/*.md`. Regenerate with `make seed-sql`. +3. `backend/prisma/sql/seed.d/*.sql` — one file per test prompt, extracted from + the `## Data Seeding` blocks in `prompts/testing/unit/*.md`. Regenerate with + `make seed-sql`. Steps 1 and 2 run on every `docker compose up` and are near no-ops when nothing changed. Step 3 is skipped once a marker row exists. +Each seed block is applied in its own transaction, because the prompts were +written to run standalone and a few have drifted from the schema. At the time +of writing 19 of 23 apply; the rest are reported by name, e.g. + +``` +! 16-support.sql: ERROR: relation "support_tickets" does not exist +! 23-dashboard.sql: ERROR: column "totalRevenue" of relation "ConsultantProfile" does not exist +``` + +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. + To refresh the vendored SQL after an upstream change: ```bash diff --git a/scripts/gen-dev-seed.sh b/scripts/gen-dev-seed.sh index 4dd3c98..64298dd 100755 --- a/scripts/gen-dev-seed.sh +++ b/scripts/gen-dev-seed.sh @@ -1,47 +1,66 @@ #!/bin/bash -# Regenerate backend/prisma/sql/seed-dev.sql from the test prompts. +# Regenerate backend/prisma/sql/seed.d/ from the test prompts. # # The `## Data Seeding` blocks in prompts/testing/unit/*.md are the fixtures the # agent-driven test prompts already assume exist. Rather than maintain a second, -# drifting copy of that data, this script concatenates them into a single seed -# applied to the local Postgres by docker/db-init. +# drifting copy of that data, this script extracts them. +# +# One file per prompt, NOT one concatenated script: the prompts were written to +# run standalone, so some reference tables that no longer exist in +# backend/prisma/schema.prisma. docker/db-init applies each file in its own +# transaction and reports the ones that do not apply, so drift is visible +# instead of aborting the whole seed. # # Run after editing any prompts/testing/unit/*.md seeding block. set -euo pipefail cd "$(dirname "$0")/.." -OUT=backend/prisma/sql/seed-dev.sql -SRC_GLOB=prompts/testing/unit/*.md - -{ - cat <<'HDR' --- ───────────────────────────────────────────────────────────── --- Local development seed. --- --- HARVESTED from the `## Data Seeding` blocks of prompts/testing/unit/*.md, --- which are the fixtures the E2E/agent test prompts already assume exist. --- Regenerate with: ./scripts/gen-dev-seed.sh --- --- Each source file uses its own `test_unit__*` id prefix, so the blocks --- are self-contained and safe to concatenate in file order. --- --- NOT idempotent on its own — docker/db-init/entrypoint.sh guards it with a --- marker-row check so a re-run of `docker compose up` does not re-insert. --- ───────────────────────────────────────────────────────────── +OUT_DIR=backend/prisma/sql/seed.d +rm -rf "$OUT_DIR" +mkdir -p "$OUT_DIR" -BEGIN; -HDR +count=0 +for f in prompts/testing/unit/*.md; do + base=$(basename "$f" .md) + out="$OUT_DIR/${base}.sql" - for f in $SRC_GLOB; do - printf '\n-- ===== %s =====\n' "$(basename "$f")" - # Take the fenced ```sql block that follows the "## Data Seeding" heading. - # `-- execute_sql` is an agent directive, not SQL, so drop it. + { + printf -- '-- Generated from prompts/testing/unit/%s by scripts/gen-dev-seed.sh.\n' "$(basename "$f")" + printf -- '-- Do not edit directly; edit the prompt and regenerate.\n\n' + printf -- 'BEGIN;\n\n' + # The fenced ```sql block following the "## Data Seeding" heading. + # `-- execute_sql` is an agent directive, not SQL. awk '/^## Data Seeding/{flag=1;next} flag&&/^```sql/{inb=1;next} inb&&/^```/{exit} inb{print}' "$f" \ | grep -v '^-- execute_sql$' - done + printf -- '\nCOMMIT;\n' + } > "$out" + + # ── Disambiguate Domain.name ────────────────────────────────────────────── + # 17 prompts insert a Domain with a distinct id but the same name, + # 'Technology'. Domain.name is UNIQUE (Domain_name_key), and the prompts' + # `ON CONFLICT (id) DO NOTHING` does not cover a name collision, so they + # cannot coexist in one database. The rows cannot simply be dropped either: + # each block's ConsultantProfile references its OWN domainId. Suffixing the + # name with the row id keeps every foreign key intact and the names unique. + python3 - "$out" <<'PY' +import re, sys +p = sys.argv[1] +s = open(p).read() +s = re.sub( + r'INSERT INTO "Domain"[^;]*;', + lambda m: re.sub( + r"\('([^']+)',\s*'([^']+)'", + lambda t: "('%s', '%s [%s]'" % (t.group(1), t.group(2), t.group(1)), + m.group(0), + ), + s, +) +open(p, 'w').write(s) +PY - printf '\nCOMMIT;\n' -} > "$OUT" + count=$((count + 1)) +done -echo "Wrote $OUT ($(wc -l < "$OUT" | tr -d ' ') lines, $(grep -c 'INSERT INTO\|^UPDATE' "$OUT") statements)" +echo "Wrote $count files to $OUT_DIR/" +grep -c 'INSERT INTO\|^UPDATE' "$OUT_DIR"/*.sql | awk -F: '{n+=$2} END{print " " n " statements total"}' From 4ee8b03135ef13a7f544822078e25a30615391f7 Mon Sep 17 00:00:00 2001 From: Kaustav Ghosh Date: Sat, 25 Jul 2026 01:00:08 +0530 Subject: [PATCH 3/4] docs(dev): record host free-space requirement and containerd corruption 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) --- docs/getting-started/05-docker-local-dev.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/getting-started/05-docker-local-dev.md b/docs/getting-started/05-docker-local-dev.md index 67a8d2f..89676a0 100644 --- a/docs/getting-started/05-docker-local-dev.md +++ b/docs/getting-started/05-docker-local-dev.md @@ -40,6 +40,26 @@ changed. Docker cannot fix that — see [Codegen](#codegen) below for what does. Docker Desktop with **at least 8 GB RAM, 4 CPUs and 60 GB disk**. The defaults are too small: a Flutter web build and build_runner over 500 files will OOM. +You also need **real free space on the host** — around 15 GB. The Flutter image +is roughly 4 GB, and Docker's VM disk grows on demand. If the host fills up +while Docker is writing, the writes fail *inside* the VM and corrupt its +containerd content store. The symptom is unmistakable: + +``` +failed to solve: failed to compute cache key: input/output error +Error response from daemon: ... blob sha256:… : input/output error +write /var/lib/desktop-containerd/…/meta.db: input/output error +``` + +Once that happens, `docker system prune` cannot fix it — pruning has to read +the very blobs that are unreadable — and Docker Desktop may stop launching. +Recover with **Docker Desktop → Troubleshoot → Reset to factory defaults** (or +*Purge data*), then free host space before retrying. Nothing here is lost by +that reset: `make db` rebuilds the image in about 20 s and reprovisions the +database in under a second. + +`make doctor` reports the daemon state before you start. + ## What runs where | | Where | Why | From f656a9f4b205f810c677a2d8090d29083450bd2e Mon Sep 17 00:00:00 2001 From: Kaustav Ghosh Date: Sat, 25 Jul 2026 12:17:13 +0530 Subject: [PATCH 4/4] fix(backend): two bugs found by actually running the container 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) --- backend/main.dart | 9 +- backend/pubspec.lock | 1357 +++++++++++++++++++++++++++ backend/scripts/ensure-generated.sh | 7 +- 3 files changed, 1370 insertions(+), 3 deletions(-) create mode 100644 backend/pubspec.lock diff --git a/backend/main.dart b/backend/main.dart index 1e7698a..5e17c91 100644 --- a/backend/main.dart +++ b/backend/main.dart @@ -1,4 +1,9 @@ -import 'dart:io' show File, HttpServer, InternetAddress, Platform; +import 'dart:io' show File, HttpServer, InternetAddress; +// Prefixed deliberately: lib/generated/index.dart exports a `Platform` enum +// from the Prisma schema, and database_client.dart re-exports it, so an +// unprefixed `Platform` here resolves to that enum instead of dart:io's. +// Same gotcha documented in lib/database/database_client.dart. +import 'dart:io' as io show Platform; import 'package:backend/database/database_client.dart'; import 'package:backend/services/auth/auth_service.dart'; @@ -30,7 +35,7 @@ Future run(Handler handler, InternetAddress ip, int port) async { final env = DotEnv(); if (File('.env').existsSync()) env.load(['.env']); if (File('.env.local').existsSync()) env.load(['.env.local']); - env.addAll(Platform.environment); + env.addAll(io.Platform.environment); // Initialize Sentry for error tracking (optional) await SentryLogger.init(env['SENTRY_DSN']); diff --git a/backend/pubspec.lock b/backend/pubspec.lock new file mode 100644 index 0000000..c46075a --- /dev/null +++ b/backend/pubspec.lock @@ -0,0 +1,1357 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + url: "https://pub.dev" + source: hosted + version: "7.7.1" + app_links: + dependency: transitive + description: + name: app_links + sha256: f8db46d2ea9ff6f3a37191a7fd5b7813da1253ace8c32c1b9eadace41d1188ea + url: "https://pub.dev" + source: hosted + version: "7.2.1" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + bcrypt: + dependency: "direct main" + description: + name: bcrypt + sha256: "6073a700cbbc59f1d4ab27cd532755e3de5e676c4941f535f351374df849270b" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" + build: + dependency: transitive + description: + name: build + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 + url: "https://pub.dev" + source: hosted + version: "4.1.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + url: "https://pub.dev" + source: hosted + version: "9.1.2" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.dev" + source: hosted + version: "4.11.1" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + connectivity_plus: + dependency: transitive + description: + name: connectivity_plus + sha256: "762c99f890ca8bf87f7337236f99edd42793843bc6c3631da294a76653a54bd0" + url: "https://pub.dev" + source: hosted + version: "7.3.1" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: "3c09627c536d22fd24691a905cdd8b14520de69da52c7a97499c8be5284a32ed" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_frog: + dependency: "direct main" + description: + name: dart_frog + sha256: "4ab2323ad74f935f5f76cb2de7e699d0319245e8029753878b0ddc35984f9cfe" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + dart_frog_lint: + dependency: "direct dev" + description: + name: dart_frog_lint + sha256: a3dd7762f8e430de79e047036f86404b130567d3be6cd1bca75c2bdb51a69b62 + url: "https://pub.dev" + source: hosted + version: "0.1.2" + dart_jsonwebtoken: + dependency: "direct main" + description: + name: dart_jsonwebtoken + sha256: "00a0812d2aeaeb0d30bcbc4dd3cee57971dbc0ab2216adf4f0247f37793f15ef" + url: "https://pub.dev" + source: hosted + version: "2.17.0" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" + url: "https://pub.dev" + source: hosted + version: "0.7.14" + dotenv: + dependency: "direct main" + description: + name: dotenv + sha256: "379e64b6fc82d3df29461d349a1796ecd2c436c480d4653f3af6872eccbc90e1" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + ed25519_edwards: + dependency: transitive + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_hooks: + dependency: transitive + description: + name: flutter_hooks + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" + url: "https://pub.dev" + source: hosted + version: "0.21.3+1" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" + url: "https://pub.dev" + source: hosted + version: "2.5.8" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: e9685e9ab852a8b8e0579c867f6c9155da27317b294b3df796a536b8b8e0d253 + url: "https://pub.dev" + source: hosted + version: "2.6.4" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: "360bba9606e58d5acc59a033ab64ae3cf571a6868f7a7267cb8e9a204b7bf1b2" + url: "https://pub.dev" + source: hosted + version: "2.26.0" + gql: + dependency: transitive + description: + name: gql + sha256: "67c32325eb55c15f526f0f5e7d8b38a463dbff2ec3c2e046be4a1a95f0dc93d1" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + gql_dedupe_link: + dependency: transitive + description: + name: gql_dedupe_link + sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96" + url: "https://pub.dev" + source: hosted + version: "2.0.4-alpha+1715521079596" + gql_error_link: + dependency: transitive + description: + name: gql_error_link + sha256: dd0f3fbfbcec848ea050507470cdb5d3dc47d29544ae11044a1c883cbe159ccc + url: "https://pub.dev" + source: hosted + version: "1.0.1" + gql_exec: + dependency: transitive + description: + name: gql_exec + sha256: "394944626fae900f1d34343ecf2d62e44eb984826189c8979d305f0ae5846e38" + url: "https://pub.dev" + source: hosted + version: "1.1.1-alpha+1699813812660" + gql_http_link: + dependency: transitive + description: + name: gql_http_link + sha256: "07635e85a4f313836904961904417fd27844fe8f68f77b410a4e6b81d8e9202e" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + gql_link: + dependency: transitive + description: + name: gql_link + sha256: "0730276ce3a6a0ced073194ff923a8d99b3c78e442cbf096eb54fd0c3fa9f974" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + gql_transform_link: + dependency: transitive + description: + name: gql_transform_link + sha256: b3bb06a6991bc5c9d877e2757455f80e2c14dc684b8327bedae4f4ee67afae8b + url: "https://pub.dev" + source: hosted + version: "1.0.1" + graphql: + dependency: transitive + description: + name: graphql + sha256: a7cb0b5e8719546bf8d4edf5f57c3690ddf0fcce379c0d9d2287fdab73481090 + url: "https://pub.dev" + source: hosted + version: "5.2.4" + graphql_flutter: + dependency: transitive + description: + name: graphql_flutter + sha256: "4164962170998bc88bed833d1aa6efc5c3a85cbc8e66a8e62f790b43f4953980" + url: "https://pub.dev" + source: hosted + version: "5.3.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + gtk: + dependency: transitive + description: + name: gtk + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + hive_ce: + dependency: transitive + description: + name: hive_ce + sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418" + url: "https://pub.dev" + source: hosted + version: "2.19.3" + hooks: + dependency: transitive + description: + name: hooks + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_methods: + dependency: transitive + description: + name: http_methods + sha256: "6bccce8f1ec7b5d701e7921dca35e202d425b57e317ba1a37f2638590e29e566" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + isolate_channel: + dependency: transitive + description: + name: isolate_channel + sha256: a9d3d620695bc984244dafae00b95e4319d6974b2d77f4b9e1eb4f2efe099094 + url: "https://pub.dev" + source: hosted + version: "0.6.1" + jni: + dependency: transitive + description: + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c + url: "https://pub.dev" + source: hosted + version: "6.9.5" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.dev" + source: hosted + version: "1.18.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + mysql1: + dependency: transitive + description: + name: mysql1 + sha256: "68aec7003d2abc85769bafa1777af3f4a390a90c31032b89636758ff8eb839e9" + url: "https://pub.dev" + source: hosted + version: "0.20.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + normalize: + dependency: transitive + description: + name: normalize + sha256: "703f0af9e6f43a5a71536e977b945238bc89f1a941347e7ba467865a20cc1a9f" + url: "https://pub.dev" + source: hosted + version: "0.10.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.dev" + source: hosted + version: "9.4.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + passkeys_platform_interface: + dependency: transitive + description: + name: passkeys_platform_interface + sha256: e810520c7b79dca629fdc266958564eedd77dc85a955f5e94430dfccb92eef68 + url: "https://pub.dev" + source: hosted + version: "2.9.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.dev" + source: hosted + version: "7.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + postgres: + dependency: "direct main" + description: + name: postgres + sha256: "123de5cbadc56a7e8d9fa485c780b6b56940b4081f4c74f3a5578682757c299b" + url: "https://pub.dev" + source: hosted + version: "3.5.12" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: "10e3f195e131eec944fa872644aed89b33b5be998f3fc77c87325db3927bc646" + url: "https://pub.dev" + source: hosted + version: "2.8.0" + prisma_flutter_connector: + dependency: "direct main" + description: + name: prisma_flutter_connector + sha256: be501a935ef506f0e55a29b24c1e0fa64c23cb4e16ba20b44ecb6b22d104bc96 + url: "https://pub.dev" + source: hosted + version: "0.6.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: "0cfbe0047e2591eba348938e9b4e620d5b1a8df6c374757e8eb8645252b8387f" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sentry: + dependency: "direct main" + description: + name: sentry + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" + url: "https://pub.dev" + source: hosted + version: "8.14.2" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + url: "https://pub.dev" + source: hosted + version: "2.4.27" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_hotreload: + dependency: transitive + description: + name: shelf_hotreload + sha256: "449f68ce2d087a030c2bfbb40e6925c63fac3dcb502a67388a7139ce72102e7a" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + url: "https://pub.dev" + source: hosted + version: "1.3.7" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "58a799e6ac17dd32fbab93813d39ed835a75ccc0f8f85b8955fe318c6712b082" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: d0548f9d7422a2dae99ec6f8b0a3074463b132d216fa5ba0d230eeefc901983b + url: "https://pub.dev" + source: hosted + version: "2.4.3" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "5bf6a55c166e73bf651ba7ec3ed486e577620e3dc8f3a9c6a258a8031b624590" + url: "https://pub.dev" + source: hosted + version: "2.5.11" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: c86ca18b8f666bbf903924687fe21cc16fc385d086005067e26619ca530bef9f + url: "https://pub.dev" + source: hosted + version: "2.4.3+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: f84939f84350d92d04416f8bc4dc52d3896aec7716cc9e80cf0146342139dc50 + url: "https://pub.dev" + source: hosted + version: "2.4.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "221263cfbe0c01575b7d7fe7543e44abff380eb4d38d936372844a1caa85ba12" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + supabase: + dependency: transitive + description: + name: supabase + sha256: "3879996256325fcc9abb03b62b12c07e4eaae2e008e1bf043cc1525184159a43" + url: "https://pub.dev" + source: hosted + version: "2.14.0" + supabase_flutter: + dependency: transitive + description: + name: supabase_flutter + sha256: c9916c1cd512ebf3107ec0f83c9dca13d2e1e789629c2a5df896586bff46ce5f + url: "https://pub.dev" + source: hosted + version: "2.16.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "61894a1956de6b4fc1aefd0892e109514a1a706cbece3ac59decd90ff5a7a423" + url: "https://pub.dev" + source: hosted + version: "3.4.1+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: b413d49b73867ac08dd2f9890efd3cc11f2a0e577618d50843440a1fb3776c32 + url: "https://pub.dev" + source: hosted + version: "6.3.32" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" + url: "https://pub.dev" + source: hosted + version: "2.4.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: eaa26beb5990b25a49d942374fd5a0c5aa67a837e03b14b4c26134aaa1ed01a9 + url: "https://pub.dev" + source: hosted + version: "2.1.1" +sdks: + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/backend/scripts/ensure-generated.sh b/backend/scripts/ensure-generated.sh index 1b65d8e..d51df68 100755 --- a/backend/scripts/ensure-generated.sh +++ b/backend/scripts/ensure-generated.sh @@ -62,7 +62,12 @@ echo "[codegen] stale or forced — regenerating" dart pub get # Full wipe ONLY on a real input change. -rm -rf lib/generated +# +# Clear the CONTENTS rather than the directory itself: under docker-compose +# lib/generated is a named-volume mount point, and unlinking a mount point +# fails with "Device or resource busy". Emptying it works in both places. +mkdir -p lib/generated +find lib/generated -mindepth 1 -delete dart run prisma_flutter_connector:generate \ --schema prisma/schema.prisma \