-- v1.1.8 User Management — data-plane app users. -- -- Distinct from `admin_users` (control-plane operators). These are the -- end-users of apps built on PiCloud — created and managed by user -- scripts via the `users::*` SDK, surfaced to the dashboard via -- `/api/v1/admin/apps/{id}/users/*`. -- -- Identity tuple is `(app_id, id)`; uniqueness is enforced on -- `(app_id, lower(email))` so the same email can exist across two apps -- but not twice within one app. Email case is preserved in storage and -- normalized only at the index / lookup boundary. -- -- Password hash is Argon2id PHC (same algorithm as `admin_users` — the -- script-end-user trust shape and the operator-account trust shape -- happen to coincide on the hashing primitive even though everything -- else about the two tables is independent). CREATE TABLE app_users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), app_id UUID NOT NULL REFERENCES apps(id) ON DELETE CASCADE, email TEXT NOT NULL, password_hash TEXT NOT NULL, display_name TEXT, email_verified_at TIMESTAMPTZ, last_login_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX idx_app_users_app_email_lower ON app_users (app_id, lower(email)); CREATE INDEX idx_app_users_app ON app_users (app_id);