import { NextRequest, NextResponse } from 'next/server'; import { Pool } from 'pg'; const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, }); export default async function handler(req: NextRequest) { if (req.method !== 'POST') { return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), { status: 405, headers: { 'Content-Type': 'application/json' }, }); } const client = await pool.connect(); try { console.log('Setting up user authentication schema...'); // Execute the schema setup await client.query(` -- Users table CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, first_name VARCHAR(50), last_name VARCHAR(50), avatar_url TEXT, is_active BOOLEAN DEFAULT true, email_verified 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 ); -- Roles table CREATE TABLE IF NOT EXISTS roles ( id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- User roles junction table (many-to-many) CREATE TABLE IF NOT EXISTS user_roles ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, assigned_by INTEGER REFERENCES users(id), UNIQUE(user_id, role_id) ); -- Permissions table CREATE TABLE IF NOT EXISTS permissions ( id SERIAL PRIMARY KEY, name VARCHAR(100) UNIQUE NOT NULL, description TEXT, resource VARCHAR(50), action VARCHAR(50), created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); -- Role permissions junction table CREATE TABLE IF NOT EXISTS role_permissions ( id SERIAL PRIMARY KEY, role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE, UNIQUE(role_id, permission_id) ); -- Sessions table for JWT token management CREATE TABLE IF NOT EXISTS user_sessions ( id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, token_hash VARCHAR(255) NOT NULL, expires_at TIMESTAMP WITH TIME ZONE NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, last_used TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, user_agent TEXT, ip_address INET ); `); // Update existing tables to include user ownership await client.query(` ALTER TABLE user_collections ADD COLUMN IF NOT EXISTS created_by INTEGER REFERENCES users(id); ALTER TABLE user_decks ADD COLUMN IF NOT EXISTS created_by INTEGER REFERENCES users(id); `); // Insert default roles await client.query(` INSERT INTO roles (name, description) VALUES ('user', 'Standard user with basic permissions'), ('admin', 'Administrator with full system access') ON CONFLICT (name) DO NOTHING; `); // Insert default permissions const permissions = [ ['cards.read', 'View cards', 'cards', 'read'], ['cards.create', 'Create new cards', 'cards', 'create'], ['cards.update', 'Update existing cards', 'cards', 'update'], ['cards.delete', 'Delete cards', 'cards', 'delete'], ['collections.read', 'View collections', 'collections', 'read'], ['collections.create', 'Create collections', 'collections', 'create'], ['collections.update', 'Update collections', 'collections', 'update'], ['collections.delete', 'Delete collections', 'collections', 'delete'], ['decks.read', 'View decks', 'decks', 'read'], ['decks.create', 'Create decks', 'decks', 'create'], ['decks.update', 'Update decks', 'decks', 'update'], ['decks.delete', 'Delete decks', 'decks', 'delete'], ['users.read', 'View users', 'users', 'read'], ['users.create', 'Create users', 'users', 'create'], ['users.update', 'Update users', 'users', 'update'], ['users.delete', 'Delete users', 'users', 'delete'], ['admin.access', 'Access admin panel', 'admin', 'access'] ]; for (const [name, description, resource, action] of permissions) { await client.query(` INSERT INTO permissions (name, description, resource, action) VALUES ($1, $2, $3, $4) ON CONFLICT (name) DO NOTHING; `, [name, description, resource, action]); } // Assign permissions to roles // User role permissions await client.query(` INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r, permissions p WHERE r.name = 'user' AND p.name IN ( 'cards.read', 'collections.read', 'collections.create', 'collections.update', 'collections.delete', 'decks.read', 'decks.create', 'decks.update', 'decks.delete' ) ON CONFLICT (role_id, permission_id) DO NOTHING; `); // Admin role permissions (all permissions) await client.query(` INSERT INTO role_permissions (role_id, permission_id) SELECT r.id, p.id FROM roles r, permissions p WHERE r.name = 'admin' ON CONFLICT (role_id, permission_id) DO NOTHING; `); // Create indexes for performance await client.query(` CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); CREATE INDEX IF NOT EXISTS idx_users_username ON users(username); CREATE INDEX IF NOT EXISTS idx_user_roles_user_id ON user_roles(user_id); CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id); CREATE INDEX IF NOT EXISTS idx_user_sessions_expires_at ON user_sessions(expires_at); `); // Create updated_at trigger function await client.query(` CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = CURRENT_TIMESTAMP; RETURN NEW; END; $$ language 'plpgsql'; `); // Add updated_at triggers await client.query(` DROP TRIGGER IF EXISTS update_users_updated_at ON users; CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); `); console.log('✅ User authentication schema setup completed!'); return new NextResponse(JSON.stringify({ success: true, message: 'User authentication schema setup completed successfully' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); } catch (error) { console.error('Schema setup failed:', error); return new NextResponse(JSON.stringify({ success: false, error: 'Schema setup failed', details: (error as Error).message }), { status: 500, headers: { 'Content-Type': 'application/json' }, }); } finally { client.release(); } }