e5db66e74a
Deploy to route.crispygoat.com / deploy (push) Successful in 4m27s
The create-user action in src/actions/admin/users.ts was written against a richer admin_users schema than the one that landed in 0001_init.sql. The 0001 schema has just `name` plus the role-derived can_manage_<X> columns; the action also references display_name, phone_number, brand_id, can_manage_pickup/messages/refunds/users, active, must_change_password, auth_provider, auth_subject, and last_login. Result: 'column "display_name" of relation "admin_users" does not exist' on any create-user submit. Migration 0043 adds the missing columns with sensible defaults (ADD COLUMN IF NOT EXISTS, so re-runnable). The four extra can_manage_* flags are vestigial — getAdminUser() in lib/admin-permissions.ts derives per-user permissions from the role via permissionsForRole() and never reads them — but they exist so the create-user form's per-user toggles persist, and the migration header documents the cleanup target. The db/schema/brands.ts Drizzle adminUsers definition is extended in lockstep so the inferred TS types stay in sync with the table. brand_id is left as a denormalized column (the link table admin_user_brands is the source of truth and is what getAdminUser() reads); getAdminUsers() in the action joins on au.brand_id so keeping the column avoids rewriting the SELECT. Gitea CI runs scripts/migrate.js before the build, so this lands on prod automatically on the next push.
71 lines
3.6 KiB
SQL
71 lines
3.6 KiB
SQL
-- 0043_admin_users_extra_columns.sql
|
|
--
|
|
-- The application-layer admin user CRUD in `src/actions/admin/users.ts`
|
|
-- was written for a richer `admin_users` schema than the one that
|
|
-- landed in 0001_init.sql. The 0001 schema has just `name` plus the
|
|
-- role-derived flag columns (`can_manage_orders`, `can_manage_products`,
|
|
-- `can_manage_stops`, `can_manage_customers`, `can_manage_wholesale`,
|
|
-- `can_manage_billing`, `can_manage_settings`, `can_manage_water_log`,
|
|
-- `can_manage_time_tracking`, `can_manage_route_trace`,
|
|
-- `can_manage_reports`, `can_manage_communications`).
|
|
--
|
|
-- The action also references these columns, which are not in 0001:
|
|
--
|
|
-- display_name, phone_number, brand_id,
|
|
-- can_manage_pickup, can_manage_messages,
|
|
-- can_manage_refunds, can_manage_users,
|
|
-- active, must_change_password,
|
|
-- auth_provider, auth_subject, last_login
|
|
--
|
|
-- This migration adds all of them so the create-user, list-users,
|
|
-- update-user, and read-user flows work against the actual table.
|
|
-- Re-runnable: each ALTER uses ADD COLUMN IF NOT EXISTS.
|
|
--
|
|
-- Notes on the new columns:
|
|
--
|
|
-- - `display_name` is what the UI shows in the user list / "logged in
|
|
-- as" labels. The original `name` column from 0001 is left in place
|
|
-- (Drizzle `adminUsers.name` still maps to it; `getAdminUser()` reads
|
|
-- it) — `display_name` is the column the action's SQL touches so it
|
|
-- stays the writable surface for the create-user form. Both can
|
|
-- coexist; the next cleanup pass can collapse them.
|
|
--
|
|
-- - The four extra `can_manage_*` flags (pickup / messages / refunds /
|
|
-- users) are not consulted by `getAdminUser()` — that lookup uses
|
|
-- `permissionsForRole(role)` from `lib/admin-permissions.ts`, which
|
|
-- derives flags from the user's role rather than from these
|
|
-- columns. They are kept on the row so the create-user form's
|
|
-- per-user permission toggles can persist; they simply do not yet
|
|
-- affect runtime authorization. Cleanup target: either move them into
|
|
-- a per-user permission table that the role-derived lookup merges
|
|
-- in, or drop them from the form.
|
|
--
|
|
-- - `brand_id` is a denormalization of `admin_user_brands` (the link
|
|
-- table is the source of truth for brand assignment, and
|
|
-- `getAdminUser()` reads from it). The action writes both, which is
|
|
-- redundant but not incorrect — keeping it makes the action's
|
|
-- read-after-write (`getAdminUsers` joins on `au.brand_id`) work
|
|
-- without rewriting the SELECT.
|
|
--
|
|
-- - `must_change_password` defaults to FALSE at the column level; the
|
|
-- action sets it to TRUE on every create so the user is forced to
|
|
-- set a new password on first sign-in.
|
|
|
|
ALTER TABLE admin_users
|
|
ADD COLUMN IF NOT EXISTS display_name TEXT,
|
|
ADD COLUMN IF NOT EXISTS phone_number TEXT,
|
|
ADD COLUMN IF NOT EXISTS brand_id UUID REFERENCES brands(id) ON DELETE SET NULL,
|
|
ADD COLUMN IF NOT EXISTS can_manage_pickup BOOLEAN NOT NULL DEFAULT true,
|
|
ADD COLUMN IF NOT EXISTS can_manage_messages BOOLEAN NOT NULL DEFAULT true,
|
|
ADD COLUMN IF NOT EXISTS can_manage_refunds BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS can_manage_users BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS active BOOLEAN NOT NULL DEFAULT true,
|
|
ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT false,
|
|
ADD COLUMN IF NOT EXISTS auth_provider TEXT,
|
|
ADD COLUMN IF NOT EXISTS auth_subject TEXT,
|
|
ADD COLUMN IF NOT EXISTS last_login TIMESTAMPTZ;
|
|
|
|
-- Helpful index for the brand-name lookup the user list does
|
|
-- (LEFT JOIN brands b ON b.id = au.brand_id).
|
|
CREATE INDEX IF NOT EXISTS admin_users_brand_id_idx ON admin_users (brand_id);
|