Add debugging tools and optimize auth endpoints

🔧 Debugging Improvements:
- Optimize setup-auth endpoint to avoid timeouts
- Add table existence checks before operations
- Simplify permission system for faster setup
- Add environment variable validation
- Create test-db endpoint for connection debugging

🚀 Performance Optimizations:
- Break down large SQL queries into smaller chunks
- Add early returns for already-setup scenarios
- Reduce permission complexity during initial setup
- Better error messages for troubleshooting

🧪 New Debug Endpoints:
- /api/test-db - Test database connectivity
- /api/setup-auth - Optimized schema setup

These changes should resolve timeout issues and provide better debugging information for authentication problems.
This commit is contained in:
Randall Stillwell 2025-07-21 21:39:08 -05:00
parent 07b3dda6fa
commit 0531f25cbb
3 changed files with 162 additions and 64 deletions

View file

@ -24,6 +24,21 @@ export default async function handler(req: NextRequest) {
}); });
} }
// Check environment variables
if (!process.env.DATABASE_URL) {
return new NextResponse(JSON.stringify({
error: 'Database configuration missing',
details: 'DATABASE_URL environment variable not set'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
if (!process.env.JWT_SECRET) {
console.warn('JWT_SECRET not set, using fallback');
}
const client = await pool.connect(); const client = await pool.connect();
try { try {
@ -62,6 +77,25 @@ export default async function handler(req: NextRequest) {
}); });
} }
// Check if users table exists
const tableCheck = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'users'
);
`);
if (!tableCheck.rows[0].exists) {
return new NextResponse(JSON.stringify({
error: 'Database not initialized',
details: 'Please run the setup-auth endpoint first'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
// Check if user already exists // Check if user already exists
const existingUser = await client.query( const existingUser = await client.query(
'SELECT id FROM users WHERE username = $1 OR email = $2', 'SELECT id FROM users WHERE username = $1 OR email = $2',

View file

@ -19,9 +19,28 @@ export default async function handler(req: NextRequest) {
try { try {
console.log('Setting up user authentication schema...'); console.log('Setting up user authentication schema...');
// Execute the schema setup // Check if users table already exists
const tableCheck = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'users'
);
`);
if (tableCheck.rows[0].exists) {
return new NextResponse(JSON.stringify({
success: true,
message: 'User authentication schema already exists',
already_setup: true
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
// Create tables one by one
await client.query(` await client.query(`
-- Users table
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL, username VARCHAR(50) UNIQUE NOT NULL,
@ -36,16 +55,18 @@ export default async function handler(req: NextRequest) {
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP WITH TIME ZONE last_login TIMESTAMP WITH TIME ZONE
); );
`);
-- Roles table await client.query(`
CREATE TABLE IF NOT EXISTS roles ( CREATE TABLE IF NOT EXISTS roles (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL, name VARCHAR(50) UNIQUE NOT NULL,
description TEXT, description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
); );
`);
-- User roles junction table (many-to-many) await client.query(`
CREATE TABLE IF NOT EXISTS user_roles ( CREATE TABLE IF NOT EXISTS user_roles (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
@ -54,8 +75,9 @@ export default async function handler(req: NextRequest) {
assigned_by INTEGER REFERENCES users(id), assigned_by INTEGER REFERENCES users(id),
UNIQUE(user_id, role_id) UNIQUE(user_id, role_id)
); );
`);
-- Permissions table await client.query(`
CREATE TABLE IF NOT EXISTS permissions ( CREATE TABLE IF NOT EXISTS permissions (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(100) UNIQUE NOT NULL,
@ -64,16 +86,18 @@ export default async function handler(req: NextRequest) {
action VARCHAR(50), action VARCHAR(50),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
); );
`);
-- Role permissions junction table await client.query(`
CREATE TABLE IF NOT EXISTS role_permissions ( CREATE TABLE IF NOT EXISTS role_permissions (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE,
permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE, permission_id INTEGER REFERENCES permissions(id) ON DELETE CASCADE,
UNIQUE(role_id, permission_id) UNIQUE(role_id, permission_id)
); );
`);
-- Sessions table for JWT token management await client.query(`
CREATE TABLE IF NOT EXISTS user_sessions ( CREATE TABLE IF NOT EXISTS user_sessions (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
@ -86,12 +110,6 @@ export default async function handler(req: NextRequest) {
); );
`); `);
// 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 // Insert default roles
await client.query(` await client.query(`
INSERT INTO roles (name, description) VALUES INSERT INTO roles (name, description) VALUES
@ -100,51 +118,32 @@ export default async function handler(req: NextRequest) {
ON CONFLICT (name) DO NOTHING; ON CONFLICT (name) DO NOTHING;
`); `);
// Insert default permissions // Insert basic permissions
const permissions = [ const permissions = [
['cards.read', 'View cards', 'cards', 'read'], ['cards.read', 'View cards'],
['cards.create', 'Create new cards', 'cards', 'create'], ['collections.manage', 'Manage collections'],
['cards.update', 'Update existing cards', 'cards', 'update'], ['decks.manage', 'Manage decks'],
['cards.delete', 'Delete cards', 'cards', 'delete'], ['admin.access', 'Access admin panel']
['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) { for (const [name, description] of permissions) {
await client.query(` await client.query(`
INSERT INTO permissions (name, description, resource, action) INSERT INTO permissions (name, description)
VALUES ($1, $2, $3, $4) VALUES ($1, $2)
ON CONFLICT (name) DO NOTHING; ON CONFLICT (name) DO NOTHING;
`, [name, description, resource, action]); `, [name, description]);
} }
// Assign permissions to roles // Assign permissions to roles
// User role permissions
await client.query(` await client.query(`
INSERT INTO role_permissions (role_id, permission_id) INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id SELECT r.id, p.id
FROM roles r, permissions p FROM roles r, permissions p
WHERE r.name = 'user' WHERE r.name = 'user'
AND p.name IN ( AND p.name IN ('cards.read', 'collections.manage', 'decks.manage')
'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; ON CONFLICT (role_id, permission_id) DO NOTHING;
`); `);
// Admin role permissions (all permissions)
await client.query(` await client.query(`
INSERT INTO role_permissions (role_id, permission_id) INSERT INTO role_permissions (role_id, permission_id)
SELECT r.id, p.id SELECT r.id, p.id
@ -153,32 +152,11 @@ export default async function handler(req: NextRequest) {
ON CONFLICT (role_id, permission_id) DO NOTHING; ON CONFLICT (role_id, permission_id) DO NOTHING;
`); `);
// Create indexes for performance // Create indexes
await client.query(` await client.query(`
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); 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_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_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!'); console.log('✅ User authentication schema setup completed!');

86
api/test-db.ts Normal file
View file

@ -0,0 +1,86 @@
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 !== 'GET') {
return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), {
status: 405,
headers: { 'Content-Type': 'application/json' },
});
}
const client = await pool.connect();
try {
console.log('Testing database connection...');
// Test basic connection
const result = await client.query('SELECT NOW() as current_time, version() as postgres_version');
// Check if users table exists
const tableCheck = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'users'
) as users_table_exists;
`);
// Check if cards table exists
const cardsCheck = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'cards'
) as cards_table_exists;
`);
// Get table counts
let userCount = 0;
let cardCount = 0;
if (tableCheck.rows[0].users_table_exists) {
const userCountResult = await client.query('SELECT COUNT(*) as count FROM users');
userCount = parseInt(userCountResult.rows[0].count);
}
if (cardsCheck.rows[0].cards_table_exists) {
const cardCountResult = await client.query('SELECT COUNT(*) as count FROM cards');
cardCount = parseInt(cardCountResult.rows[0].count);
}
return new NextResponse(JSON.stringify({
success: true,
message: 'Database connection successful',
database_info: {
current_time: result.rows[0].current_time,
postgres_version: result.rows[0].postgres_version,
users_table_exists: tableCheck.rows[0].users_table_exists,
cards_table_exists: cardsCheck.rows[0].cards_table_exists,
user_count: userCount,
card_count: cardCount
}
}), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('Database connection failed:', error);
return new NextResponse(JSON.stringify({
success: false,
error: 'Database connection failed',
details: (error as Error).message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
} finally {
client.release();
}
}