-- 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);