← back to Lawyer Directory Builder
migrations/004_app_users.sql
40 lines
-- App users + sessions for the admin/user portals.
-- Separate from `professionals` (which is the bar-licensed-attorney directory)
-- and `organizations` (firms). An app_user MAY claim a professional/firm.
CREATE TABLE IF NOT EXISTS app_users (
id BIGSERIAL PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
full_name TEXT,
role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('user', 'admin')),
plan TEXT NOT NULL DEFAULT 'free' CHECK (plan IN ('free', 'premium', 'pro')),
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'deleted')),
organization_id BIGINT REFERENCES organizations(id) ON DELETE SET NULL,
professional_id BIGINT REFERENCES professionals(id) ON DELETE SET NULL,
bar_number TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_login_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_app_users_email ON app_users(LOWER(email));
CREATE INDEX IF NOT EXISTS idx_app_users_role ON app_users(role);
CREATE INDEX IF NOT EXISTS idx_app_users_status ON app_users(status);
CREATE TABLE IF NOT EXISTS app_sessions (
id TEXT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES app_users(id) ON DELETE CASCADE,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ip TEXT,
user_agent TEXT
);
CREATE INDEX IF NOT EXISTS idx_app_sessions_user ON app_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_app_sessions_expires ON app_sessions(expires_at);
CREATE OR REPLACE TRIGGER trg_app_users_updated
BEFORE UPDATE ON app_users
FOR EACH ROW EXECUTE FUNCTION set_updated_at();