CREATE TABLE users ( id UUID PRIMARY KEY, status VARCHAR(16) NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() ); INSERT INTO users (id) SELECT owner_id FROM student_profiles UNION SELECT owner_id FROM student_profile_defaults UNION SELECT owner_id FROM feedback_records UNION SELECT owner_id FROM feedback_sessions ON CONFLICT (id) DO NOTHING; CREATE TABLE wechat_identities ( app_id VARCHAR(64) NOT NULL, openid VARCHAR(128) NOT NULL, unionid VARCHAR(128), user_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), last_login_at TIMESTAMPTZ NOT NULL DEFAULT now(), PRIMARY KEY (app_id, openid) ); CREATE INDEX wechat_identities_user_idx ON wechat_identities (user_id); CREATE UNIQUE INDEX wechat_identities_app_unionid_idx ON wechat_identities (app_id, unionid) WHERE unionid IS NOT NULL; CREATE TABLE auth_sessions ( id UUID PRIMARY KEY, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash BYTEA NOT NULL UNIQUE CHECK (octet_length(token_hash) = 32), expires_at TIMESTAMPTZ NOT NULL, last_used_at TIMESTAMPTZ NOT NULL DEFAULT now(), revoked_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX auth_sessions_active_user_idx ON auth_sessions (user_id, expires_at DESC) WHERE revoked_at IS NULL; ALTER TABLE student_profiles ADD CONSTRAINT student_profiles_owner_fk FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT; ALTER TABLE student_profile_defaults ADD CONSTRAINT student_profile_defaults_owner_fk FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT; ALTER TABLE feedback_records ADD CONSTRAINT feedback_records_owner_fk FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT; ALTER TABLE feedback_sessions ADD CONSTRAINT feedback_sessions_owner_fk FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE RESTRICT;