CREATE TABLE IF NOT EXISTS hwlab_users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE, display_name TEXT NOT NULL DEFAULT '', password_hash TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'active', role TEXT NOT NULL DEFAULT 'user', email_verified BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CHECK (status IN ('active', 'disabled', 'pending')), CHECK (role IN ('user', 'admin')) ); CREATE TABLE IF NOT EXISTS hwlab_user_sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES hwlab_users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TIMESTAMPTZ NOT NULL, revoked_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS hwlab_user_sessions_user_id_idx ON hwlab_user_sessions(user_id); CREATE INDEX IF NOT EXISTS hwlab_user_sessions_token_hash_idx ON hwlab_user_sessions(token_hash); CREATE TABLE IF NOT EXISTS hwlab_api_keys ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES hwlab_users(id) ON DELETE CASCADE, name TEXT NOT NULL, key_prefix TEXT NOT NULL, key_hash TEXT NOT NULL UNIQUE, scopes_json JSONB NOT NULL DEFAULT '["api"]'::jsonb, status TEXT NOT NULL DEFAULT 'active', created_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_used_at TIMESTAMPTZ, revoked_at TIMESTAMPTZ, CHECK (status IN ('active', 'revoked')) ); CREATE INDEX IF NOT EXISTS hwlab_api_keys_user_id_idx ON hwlab_api_keys(user_id); CREATE INDEX IF NOT EXISTS hwlab_api_keys_key_hash_idx ON hwlab_api_keys(key_hash); CREATE TABLE IF NOT EXISTS hwlab_credit_accounts ( user_id TEXT PRIMARY KEY REFERENCES hwlab_users(id) ON DELETE CASCADE, balance_credits BIGINT NOT NULL DEFAULT 0, reserved_credits BIGINT NOT NULL DEFAULT 0, plan_id TEXT NOT NULL DEFAULT 'default', updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CHECK (balance_credits >= 0), CHECK (reserved_credits >= 0) ); CREATE TABLE IF NOT EXISTS hwlab_credit_ledger ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES hwlab_users(id) ON DELETE CASCADE, delta_credits BIGINT NOT NULL, balance_after BIGINT NOT NULL, kind TEXT NOT NULL, reason TEXT NOT NULL DEFAULT '', idempotency_key TEXT UNIQUE, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX IF NOT EXISTS hwlab_credit_ledger_user_id_idx ON hwlab_credit_ledger(user_id); CREATE TABLE IF NOT EXISTS hwlab_billing_reservations ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES hwlab_users(id) ON DELETE CASCADE, service_id TEXT NOT NULL, estimated_credits BIGINT NOT NULL, estimated_tokens BIGINT NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'reserved', idempotency_key TEXT UNIQUE, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), CHECK (estimated_credits >= 0), CHECK (estimated_tokens >= 0), CHECK (status IN ('reserved', 'completed', 'cancelled', 'expired')) ); CREATE INDEX IF NOT EXISTS hwlab_billing_reservations_user_id_idx ON hwlab_billing_reservations(user_id); CREATE TABLE IF NOT EXISTS hwlab_usage_records ( id TEXT PRIMARY KEY, reservation_id TEXT REFERENCES hwlab_billing_reservations(id) ON DELETE SET NULL, user_id TEXT NOT NULL REFERENCES hwlab_users(id) ON DELETE CASCADE, service_id TEXT NOT NULL, unit TEXT NOT NULL DEFAULT 'token', quantity BIGINT NOT NULL DEFAULT 0, credits BIGINT NOT NULL DEFAULT 0, idempotency_key TEXT UNIQUE, metadata JSONB NOT NULL DEFAULT '{}'::jsonb, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), CHECK (quantity >= 0), CHECK (credits >= 0) ); CREATE INDEX IF NOT EXISTS hwlab_usage_records_user_id_idx ON hwlab_usage_records(user_id); CREATE INDEX IF NOT EXISTS hwlab_usage_records_service_id_idx ON hwlab_usage_records(service_id);