Compare commits

..

2 Commits

Author SHA1 Message Date
BOHA
6074d6163b docs(release): document post-tarball migration hotfix procedure 2026-06-03 23:27:44 +02:00
BOHA
652e3f91fc fix(warehouse): add migration for warehouse permissions
The warehouse module v1.8.0 deploy created the warehouse tables via Prisma
migration but left the corresponding permissions (warehouse.view/operate/
manage/inventory) and admin role assignments un-inserted, because those
rows only existed in prisma/seed.ts, which is dev-only and cannot be run
against production.

This migration inserts the same 4 permissions seed.ts would (with
identical display_name and description) and assigns them to the admin
role. INSERT IGNORE makes it idempotent and preserves existing role
assignments. No data is modified or deleted.

Also strengthens CLAUDE.md: every database change or manipulation must
be a tracked Prisma migration. No external SQL, no seed-only data,
no manual fixes — reviewable, reversible, reproducible from a fresh
checkout.
2026-06-03 23:26:34 +02:00
2 changed files with 78 additions and 0 deletions

View File

@@ -289,6 +289,23 @@ When entity A embeds/references entity B, A's mutation handlers invalidate B's d
**Golden rule: NEVER use `prisma db push`.** It bypasses migration history and causes drift **Golden rule: NEVER use `prisma db push`.** It bypasses migration history and causes drift
between the schema and migration tracking. Always use `prisma migrate dev` for every schema change. between the schema and migration tracking. Always use `prisma migrate dev` for every schema change.
**Every database change or manipulation must be a tracked Prisma migration.** This includes:
- Schema changes (tables, columns, indexes, constraints) — via `prisma migrate dev`
- Permission/role/seed data changes — via a migration that does the INSERTs/DELETEs explicitly
- Lookup data, default settings, or any other row-level baseline that production needs
- Backfills, data fixes, and one-shot corrections that should be in sync across environments
**What is NOT acceptable:**
- Running raw SQL against production (`mysql -e "..."`, `npx prisma db execute`, `pm2 exec`)
- `npx prisma db seed` as the only way to populate data (seed is dev-only convenience; prod must run the same SQL via a migration)
- "I'll fix it in the seed file" or "I'll add a row manually" without a migration to back it
The reason: every change to the production database must be reviewable, reversible, and reproducible from a fresh checkout. External SQL commands and seed-only changes cause drift — prod and dev diverge, rollback gets harder with every manual change, and the next deploy surprises us.
If you need to insert/update/seed data on production, write a migration. The migration's `migration.sql` is a normal SQL file — `INSERT INTO ...`, `UPDATE ...`, `DELETE ...` are all valid. Prisma will run it via `prisma migrate deploy` like any other migration.
### Making schema changes ### Making schema changes
``` ```
@@ -369,6 +386,8 @@ npx prisma migrate resolve --applied <migration_name>
10. **Czech locale hardcoded** — Error messages, month names, and some business logic strings are Czech. This is intentional. 10. **Czech locale hardcoded** — Error messages, month names, and some business logic strings are Czech. This is intentional.
11. **Seed file is dev-only**`prisma/seed.ts` is for local dev convenience. It is **not** the production data source. Any data the production database must have (permissions, default roles, baseline rows) belongs in a migration's `migration.sql`, not in the seed file. `npx prisma db seed` must never be run against production.
--- ---
## Release Process ## Release Process
@@ -387,4 +406,34 @@ npx prisma migrate resolve --applied <migration_name>
- Apply Prisma migrations: `npx prisma migrate deploy` - Apply Prisma migrations: `npx prisma migrate deploy`
- Restart: `pm2 restart app-ts --update-env` - Restart: `pm2 restart app-ts --update-env`
### Hotfixing a migration that was added after the tarball was built
If you write a new `prisma/migrations/<timestamp>_*` folder **after** the
release tarball has already been built and shipped, that migration is
NOT in the tarball and `prisma migrate deploy` on prod will report
"No pending migrations". The release tarball re-build re-runs the whole
release, but for a one-off hotfix you can ship just the new migration
folder:
```bash
# Local
cd prisma/migrations
tar -czf /tmp/warehouse_perms_migration.tar.gz <timestamp>_<name>/
scp /tmp/warehouse_perms_migration.tar.gz boha_admin@192.168.50.100:/tmp/
# On prod
ssh boha_admin@192.168.50.100
cd /var/www/app-ts/prisma/migrations
sudo -u boha_admin tar -xzf /tmp/warehouse_perms_migration.tar.gz
cd /var/www/app-ts
npx prisma migrate deploy
pm2 restart app-ts --update-env
```
The migration is still tracked in git and applied via `prisma migrate
deploy` — the only thing the tarball is doing is delivering the
`migration.sql` to prod, which is the same mechanism the main release
uses. This is preferred over running raw SQL on prod (which the policy
in "Database Migrations" forbids).
Do not push directly to production or restart services without confirmation. Do not push directly to production or restart services without confirmation.

View File

@@ -0,0 +1,29 @@
-- Add warehouse module permissions and assign them to the admin role.
-- These rows used to be created by `prisma/seed.ts`, which is dev-only and
-- must not be run against production (per CLAUDE.md). This migration is
-- idempotent: re-running it is a no-op.
--
-- The 4 permissions match `prisma/seed.ts` lines 284-308 exactly.
-- 1. Insert the 4 warehouse permissions (INSERT IGNORE skips on duplicate
-- name, so re-running the migration is safe).
INSERT IGNORE INTO `permissions` (`name`, `display_name`, `module`, `description`, `created_at`) VALUES
('warehouse.view', 'Zobrazit sklad', 'warehouse', 'Prohlížet stav skladu, položky, reporty a historii pohybů', NOW()),
('warehouse.operate', 'Příjem a výdej', 'warehouse', 'Vytvářet a potvrzovat příjmy, výdeje a rezervace', NOW()),
('warehouse.manage', 'Správa skladu', 'warehouse', 'Spravovat katalog materiálů, dodavatele, lokace a kategorie', NOW()),
('warehouse.inventory', 'Inventura', 'warehouse', 'Vytvářet a potvrzovat inventurní sčítkání', NOW());
-- 2. Assign all 4 permissions to the admin role. INSERT IGNORE on the
-- composite primary key (role_id, permission_id) makes this idempotent
-- and preserves any other role assignments that already exist.
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission_id`)
SELECT r.id, p.id
FROM `roles` r
CROSS JOIN `permissions` p
WHERE r.name = 'admin'
AND p.name IN (
'warehouse.view',
'warehouse.operate',
'warehouse.manage',
'warehouse.inventory'
);