-- Cidadão.AI Database Schema Setup -- Author: Anderson Henrique da Silva -- Date: 2025-09-24 -- Enable UUID extension CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- Users table for authentication CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), username VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(255), is_active BOOLEAN DEFAULT true, is_admin BOOLEAN DEFAULT false, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_login TIMESTAMP WITH TIME ZONE, failed_login_attempts INTEGER DEFAULT 0, locked_until TIMESTAMP WITH TIME ZONE ); -- JWT Blacklist table CREATE TABLE IF NOT EXISTS jwt_blacklist ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), token_jti VARCHAR(255) UNIQUE NOT NULL, user_id UUID REFERENCES users(id) ON DELETE CASCADE, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, blacklisted_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, reason VARCHAR(255) ); -- Sessions table CREATE TABLE IF NOT EXISTS sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE CASCADE, token VARCHAR(255) UNIQUE NOT NULL, ip_address INET, user_agent TEXT, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_activity TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Rate limiting table CREATE TABLE IF NOT EXISTS rate_limits ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), identifier VARCHAR(255) NOT NULL, -- IP or user_id endpoint VARCHAR(255) NOT NULL, window_start TIMESTAMP WITH TIME ZONE NOT NULL, request_count INTEGER DEFAULT 1, UNIQUE(identifier, endpoint, window_start) ); -- Audit logs table CREATE TABLE IF NOT EXISTS audit_logs ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, action VARCHAR(100) NOT NULL, resource_type VARCHAR(100), resource_id VARCHAR(255), ip_address INET, user_agent TEXT, request_data JSONB, response_status INTEGER, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Investigations table (existing) CREATE TABLE IF NOT EXISTS investigations ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, title VARCHAR(500) NOT NULL, description TEXT, status VARCHAR(50) DEFAULT 'pending', type VARCHAR(100), data JSONB, results JSONB, metadata JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, completed_at TIMESTAMP WITH TIME ZONE ); -- Chat sessions table CREATE TABLE IF NOT EXISTS chat_sessions ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES users(id) ON DELETE SET NULL, title VARCHAR(500), context JSONB, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_message_at TIMESTAMP WITH TIME ZONE ); -- Chat messages table CREATE TABLE IF NOT EXISTS chat_messages ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), session_id UUID REFERENCES chat_sessions(id) ON DELETE CASCADE, role VARCHAR(50) NOT NULL, -- 'user' or 'assistant' content TEXT NOT NULL, metadata JSONB, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Create indexes for performance CREATE INDEX idx_users_email ON users(email); CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_jwt_blacklist_jti ON jwt_blacklist(token_jti); CREATE INDEX idx_jwt_blacklist_expires ON jwt_blacklist(expires_at); CREATE INDEX idx_sessions_token ON sessions(token); CREATE INDEX idx_sessions_user_id ON sessions(user_id); CREATE INDEX idx_sessions_expires ON sessions(expires_at); CREATE INDEX idx_rate_limits_identifier ON rate_limits(identifier, endpoint, window_start); CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id); CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at); CREATE INDEX idx_investigations_user_id ON investigations(user_id); CREATE INDEX idx_investigations_status ON investigations(status); CREATE INDEX idx_chat_sessions_user_id ON chat_sessions(user_id); CREATE INDEX idx_chat_messages_session_id ON chat_messages(session_id); -- Create updated_at trigger function CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; -- Apply updated_at trigger to tables CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_investigations_updated_at BEFORE UPDATE ON investigations FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); CREATE TRIGGER update_chat_sessions_updated_at BEFORE UPDATE ON chat_sessions FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- Insert default admin user (change password after setup!) INSERT INTO users (username, email, password_hash, full_name, is_admin, is_active) VALUES ( 'admin', 'admin@cidadao.ai', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewKyNiGH9jG6FnJi', -- default: Admin123! 'Administrator', true, true ) ON CONFLICT (username) DO NOTHING; -- Create cleanup function for expired data CREATE OR REPLACE FUNCTION cleanup_expired_data() RETURNS void AS $$ BEGIN -- Remove expired blacklisted tokens DELETE FROM jwt_blacklist WHERE expires_at < CURRENT_TIMESTAMP; -- Remove expired sessions DELETE FROM sessions WHERE expires_at < CURRENT_TIMESTAMP; -- Remove old rate limit records (older than 1 day) DELETE FROM rate_limits WHERE window_start < CURRENT_TIMESTAMP - INTERVAL '1 day'; END; $$ LANGUAGE plpgsql; -- Optional: Create a view for active sessions with user info CREATE OR REPLACE VIEW active_sessions AS SELECT s.id, s.user_id, u.username, u.email, s.ip_address, s.user_agent, s.created_at, s.last_activity, s.expires_at FROM sessions s JOIN users u ON s.user_id = u.id WHERE s.expires_at > CURRENT_TIMESTAMP; COMMENT ON TABLE users IS 'Application users with authentication data'; COMMENT ON TABLE jwt_blacklist IS 'Revoked JWT tokens'; COMMENT ON TABLE sessions IS 'Active user sessions'; COMMENT ON TABLE rate_limits IS 'API rate limiting tracking'; COMMENT ON TABLE audit_logs IS 'Security audit trail'; COMMENT ON TABLE investigations IS 'Government transparency investigations'; COMMENT ON TABLE chat_sessions IS 'Chat conversation sessions'; COMMENT ON TABLE chat_messages IS 'Chat message history'; -- Grant permissions (adjust based on your Supabase setup) GRANT ALL ON ALL TABLES IN SCHEMA public TO postgres; GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO postgres; GRANT ALL ON ALL FUNCTIONS IN SCHEMA public TO postgres;