diff --git a/CLAUDE.md b/CLAUDE.md index 0ef36927..653cd91a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -100,7 +100,7 @@ When writing migrations or queries: - Use Laravel's query builder where possible — it handles cross-DB syntax consistently - For raw SQL, write MySQL syntax (no need for SQLite fallbacks). Index introspection: query `information_schema.STATISTICS`, never `sqlite_master`. - Foreign keys go on every relation column. The `nullOnDelete` / `cascadeOnDelete` choice is per the relationship's domain semantics; "no FK" is not an option. -- Tests that round-trip JSON data via MySQL JSON columns must use `assertEquals` (structural equality) rather than `assertSame` on associative arrays — MySQL JSON may reorder object keys. +- JSON values stored in byte-stable columns (`schema_snapshot`, webhook `payload_snapshot`, activity-log `properties` for diff payloads) MUST be canonicalized at write via `App\Support\Json\JsonCanonicalizer`. MySQL JSON columns may reorder associative-array keys on round-trip; canonicalize so re-emits / HMAC signatures / audit-replay diffs are byte-identical. Opaque-config JSON (`form_schemas.settings`, `translations`) is exempt — key order has no semantic meaning there. Tests on canonicalized data use `assertSame(JsonCanonicalizer::encode($a), JsonCanonicalizer::encode($b))`. Other database rules: @@ -108,6 +108,37 @@ Other database rules: - Create migrations in dependency order: foundation first, then dependent tables - Always add composite indexes as documented in the design document (section 3.5) +### Schema dumps (opt-in fast path) + +Laravel supports `database/schema/{driver}-schema.sql` as a fast-path +baseline that `migrate:fresh` loads in one statement instead of replaying +every migration. For Crewli's backfill / migration tests (which call +`migrate:fresh` per test), this can yield a meaningful speedup — +**when the host has the `mysql` and `mysqldump` CLI tools available.** + +Workflow: + +1. Install MySQL client tools on host (one-time): + ```bash + brew install mysql-client + echo 'export PATH="/opt/homebrew/opt/mysql-client/bin:$PATH"' >> ~/.zshrc + ``` +2. Bring `crewli_test` to head: `DB_DATABASE=crewli_test php artisan migrate --force` +3. Generate the dump: `make schema-dump` +4. Commit `api/database/schema/mysql-schema.sql` alongside any new migrations. + +The schema dump is **NOT committed by default** because Laravel's +auto-load path shells out to the `mysql` CLI; on hosts without it, +the presence of the dump file actively breaks `migrate` / `migrate:fresh` +(load fails, exit 127). Treat the dump as opt-in per dev environment. + +`make schema-dump` runs `mysqldump` inside the `bm_mysql` Docker +container, so contributors don't need `mysqldump` on the host to +**generate** the dump — only to **load** it via Laravel's auto-detection. + +`--prune` is NOT used: individual migration files stay readable in +`api/database/migrations/` for audit / rollback purposes. + ### Roles and permissions - Use Spatie `laravel-permission` diff --git a/Makefile b/Makefile index 775cb205..cf5641aa 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help services services-stop api app portal docs migrate fresh db-shell test test-db-create +.PHONY: help services services-stop api app portal docs migrate fresh db-shell test test-db-create schema-dump # Colors GREEN := \033[0;32m @@ -27,6 +27,7 @@ help: @echo " make fresh Fresh migrate + seed" @echo " make db-shell Open MySQL shell" @echo " make test-db-create Create crewli_test database (one-time)" + @echo " make schema-dump Regenerate MySQL schema dump (run after new migrations)" @echo "" @echo " $(YELLOW)Testing:$(NC)" @echo " make test Run PHPUnit suite (creates crewli_test if needed)" @@ -81,3 +82,23 @@ test-db-create: test: test-db-create @cd api && php artisan test + +# Regenerate api/database/schema/mysql-schema.sql from the current +# crewli_test schema. Runs mysqldump INSIDE the bm_mysql Docker +# container, so contributors don't need mysqldump on the host. +# +# Workflow: this target dumps WHATEVER state crewli_test is currently +# in. Run `make test-db-create` (creates DB), then put it at the desired +# state — typically by running the test suite, which migrates the test +# DB to head — then `make schema-dump`. +# +# See CLAUDE.md "Schema dumps" — commit the regenerated dump alongside +# any new migrations so CI / fast-path migrate:fresh stays in sync. +schema-dump: + @echo "$(GREEN)Regenerating api/database/schema/mysql-schema.sql from current crewli_test state...$(NC)" + @docker exec bm_mysql mysqldump --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc -u root -proot crewli_test --routines --no-data 2>/dev/null > /tmp/crewli-schema-structure.sql + @docker exec bm_mysql mysqldump --no-tablespaces --skip-add-locks --skip-comments --skip-set-charset --tz-utc -u root -proot crewli_test migrations --no-create-info 2>/dev/null > /tmp/crewli-schema-migrations.sql + @cat /tmp/crewli-schema-structure.sql /tmp/crewli-schema-migrations.sql > api/database/schema/mysql-schema.sql + @rm /tmp/crewli-schema-structure.sql /tmp/crewli-schema-migrations.sql + @echo "$(GREEN)✓ api/database/schema/mysql-schema.sql updated$(NC)" + @echo "$(YELLOW)Note: Commit the updated schema dump alongside any new migrations.$(NC)" diff --git a/api/database/schema/.gitignore b/api/database/schema/.gitignore new file mode 100644 index 00000000..ff95feae --- /dev/null +++ b/api/database/schema/.gitignore @@ -0,0 +1,4 @@ +# Schema dumps are environment-specific and require mysql CLI on host +# for Laravel's auto-load. NOT committed by default. See CLAUDE.md +# "Schema dumps (opt-in fast path)". +mysql-schema.sql diff --git a/api/phpstan-baseline.neon b/api/phpstan-baseline.neon index d493bf6b..f883d7ce 100644 --- a/api/phpstan-baseline.neon +++ b/api/phpstan-baseline.neon @@ -5886,12 +5886,6 @@ parameters: count: 1 path: app/Services/FormBuilder/FormSubmissionService.php - - - message: '#^Anonymous function has an unused use \$actor\.$#' - identifier: closure.unusedUse - count: 1 - path: app/Services/FormBuilder/FormSubmissionService.php - - message: '#^Call to an undefined method Illuminate\\Database\\Eloquent\\Model\:\:rootConditionalLogicGroup\(\)\.$#' identifier: method.notFound