punimtag/viewer-frontend/scripts/check-and-create-databases.ts
Tanya de2144be2a feat: Add new scripts and update project structure for database management and user authentication
This commit introduces several new scripts for managing database operations, including user creation, permission grants, and data migrations. It also adds new documentation files to guide users through the setup and configuration processes. Additionally, the project structure is updated to enhance organization and maintainability, ensuring a smoother development experience for contributors. These changes support the ongoing transition to a web-based architecture and improve overall project functionality.
2026-01-06 13:53:24 -05:00

262 lines
8.4 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Client } from 'pg';
import * as dotenv from 'dotenv';
import { readFileSync } from 'fs';
import { join } from 'path';
// Load environment variables
dotenv.config({ path: '.env' });
async function checkAndCreateDatabases() {
// Get connection info from DATABASE_URL or use defaults
const mainDbUrl = process.env.DATABASE_URL || 'postgresql://postgres@localhost:5432/postgres';
const authDbUrl = process.env.DATABASE_URL_AUTH || 'postgresql://postgres@localhost:5432/postgres';
// Parse connection strings to get connection details
const parseUrl = (url: string) => {
const match = url.match(/postgresql:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/) ||
url.match(/postgresql:\/\/([^@]+)@([^:]+):(\d+)\/(.+)/) ||
url.match(/postgresql:\/\/([^@]+)@([^:]+)\/(.+)/);
if (match) {
if (match.length === 6) {
// With password
return {
user: match[1],
password: match[2],
host: match[3],
port: parseInt(match[4]),
database: match[5],
};
} else if (match.length === 5) {
// Without password, with port
return {
user: match[1],
host: match[2],
port: parseInt(match[3]),
database: match[4],
};
} else if (match.length === 4) {
// Without password, without port
return {
user: match[1],
host: match[2],
port: 5432,
database: match[3],
};
}
}
// Fallback to defaults
return {
user: 'postgres',
host: 'localhost',
port: 5432,
database: 'postgres',
};
};
const mainConfig = parseUrl(mainDbUrl);
const authConfig = parseUrl(authDbUrl);
// Connect to postgres database to check/create databases
const adminClient = new Client({
user: mainConfig.user,
password: (mainConfig as any).password,
host: mainConfig.host,
port: mainConfig.port,
database: 'postgres', // Connect to postgres database to create other databases
});
try {
console.log('🔍 Checking databases...\n');
await adminClient.connect();
console.log('✅ Connected to PostgreSQL\n');
// Check if punimtag database exists
const mainDbCheck = await adminClient.query(
"SELECT 1 FROM pg_database WHERE datname = 'punimtag'"
);
if (mainDbCheck.rows.length === 0) {
console.log('⚠️ punimtag database does not exist');
console.log(' This is the main database with photos - it should already exist.');
console.log(' If you need to create it, please do so manually or ensure your PunimTag setup is complete.\n');
} else {
console.log('✅ punimtag database exists\n');
}
// Check if punimtag_auth database exists
const authDbCheck = await adminClient.query(
"SELECT 1 FROM pg_database WHERE datname = 'punimtag_auth'"
);
if (authDbCheck.rows.length === 0) {
console.log('📦 Creating punimtag_auth database...');
await adminClient.query('CREATE DATABASE punimtag_auth');
console.log('✅ punimtag_auth database created\n');
} else {
console.log('✅ punimtag_auth database exists\n');
}
await adminClient.end();
// Now connect to punimtag_auth and create tables
const authClient = new Client({
user: authConfig.user,
password: (authConfig as any).password,
host: authConfig.host,
port: authConfig.port,
database: 'punimtag_auth',
});
try {
await authClient.connect();
console.log('🔗 Connected to punimtag_auth database\n');
// Check if users table exists
const usersTableCheck = await authClient.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'users'
);
`);
if (!usersTableCheck.rows[0].exists) {
console.log('📋 Creating tables in punimtag_auth...');
// Read and execute setup-auth-tables.sql
const setupScript = readFileSync(
join(__dirname, '../setup-auth-tables.sql'),
'utf-8'
);
// Split by semicolons and execute each statement
const statements = setupScript
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--') && !s.startsWith('\\'));
for (const statement of statements) {
if (statement.length > 0) {
try {
await authClient.query(statement);
} catch (error: any) {
// Ignore "already exists" errors
if (!error.message.includes('already exists')) {
console.error(`Error executing: ${statement.substring(0, 50)}...`);
throw error;
}
}
}
}
console.log('✅ Tables created\n');
} else {
console.log('✅ Tables already exist in punimtag_auth\n');
}
// Check for required migrations
console.log('🔍 Checking for required migrations...\n');
// Check pending_photos table
const pendingPhotosCheck = await authClient.query(`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'pending_photos'
);
`);
if (!pendingPhotosCheck.rows[0].exists) {
console.log('📋 Creating pending_photos table...');
const migrationScript = readFileSync(
join(__dirname, '../migrations/add-pending-photos-table.sql'),
'utf-8'
);
const statements = migrationScript
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--') && !s.startsWith('\\'));
for (const statement of statements) {
if (statement.length > 0) {
try {
await authClient.query(statement);
} catch (error: any) {
if (!error.message.includes('already exists')) {
throw error;
}
}
}
}
console.log('✅ pending_photos table created\n');
}
// Check email verification columns
const emailVerificationCheck = await authClient.query(`
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'public'
AND table_name = 'users'
AND column_name = 'email_verified'
`);
if (emailVerificationCheck.rows.length === 0) {
console.log('📋 Adding email verification columns...');
const migrationScript = readFileSync(
join(__dirname, '../migrations/add-email-verification-columns.sql'),
'utf-8'
);
const statements = migrationScript
.split(';')
.map(s => s.trim())
.filter(s => s.length > 0 && !s.startsWith('--') && !s.startsWith('\\'));
for (const statement of statements) {
if (statement.length > 0) {
try {
await authClient.query(statement);
} catch (error: any) {
if (!error.message.includes('already exists')) {
throw error;
}
}
}
}
console.log('✅ Email verification columns added\n');
}
console.log('🎉 Database setup complete!\n');
console.log('Next steps:');
console.log('1. Ensure your .env file has correct DATABASE_URL_AUTH');
console.log('2. Run: npm run prisma:generate:all');
console.log('3. Create admin user: npx tsx scripts/create-admin-user.ts');
} catch (error: any) {
console.error('\n❌ Error setting up tables:', error.message);
if (error.message.includes('permission denied')) {
console.error('\n⚠ Permission denied. You may need to run this as a PostgreSQL superuser.');
}
throw error;
} finally {
await authClient.end();
}
} catch (error: any) {
console.error('\n❌ Error:', error.message);
if (error.message.includes('password authentication failed')) {
console.error('\n⚠ Authentication failed. Please check:');
console.error(' 1. Database credentials in .env file');
console.error(' 2. PostgreSQL is running');
console.error(' 3. User has permission to create databases');
}
process.exit(1);
} finally {
await adminClient.end();
}
}
checkAndCreateDatabases();