UNPKG

google-oauth-cli-generator

Version:

CLI tool to quickly set up Google OAuth authentication for hackathons and projects

503 lines (429 loc) 13.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generatePostgresTemplate = generatePostgresTemplate; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); async function generatePostgresTemplate(data) { const { projectPath } = data; const backendPath = path_1.default.join(projectPath, 'backend'); // Generate database config const databaseConfigTs = `import { Pool, PoolClient } from 'pg'; let pool: Pool; export function getPool(): Pool { if (!pool) { pool = new Pool({ connectionString: process.env.DATABASE_URL || 'postgresql://username:password@localhost:5432/auth_app', ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 2000, }); pool.on('error', (err) => { console.error('Unexpected error on idle client', err); }); } return pool; } export async function connectDatabase(): Promise<void> { try { const client = await getPool().connect(); // Test connection await client.query('SELECT NOW()'); client.release(); console.log('✅ Connected to PostgreSQL'); // Create tables if they don't exist await createTables(); } catch (error) { console.error('❌ PostgreSQL connection error:', error); throw error; } } async function createTables(): Promise<void> { const client = await getPool().connect(); try { // Create users table await client.query(\` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, google_id VARCHAR(255) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, picture TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) \`); // Create indexes await client.query(\` CREATE INDEX IF NOT EXISTS idx_users_google_id ON users(google_id) \`); await client.query(\` CREATE INDEX IF NOT EXISTS idx_users_email ON users(email) \`); // Create trigger for updated_at 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' \`); await client.query(\` DROP TRIGGER IF EXISTS update_users_updated_at ON users \`); await client.query(\` CREATE TRIGGER update_users_updated_at BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_updated_at_column() \`); console.log('✅ Database tables created/verified'); } catch (error) { console.error('❌ Error creating tables:', error); throw error; } finally { client.release(); } } // Graceful shutdown process.on('SIGINT', async () => { if (pool) { await pool.end(); console.log('PostgreSQL connection pool closed.'); } process.exit(0); }); process.on('SIGTERM', async () => { if (pool) { await pool.end(); console.log('PostgreSQL connection pool closed.'); } process.exit(0); });`; await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'src', 'config', 'database.ts'), databaseConfigTs); // Generate User model const userModelTs = `import { Pool, PoolClient } from 'pg'; import { getPool } from '../config/database'; export interface IUser { id: number; google_id: string; name: string; email: string; picture: string; created_at: Date; updated_at: Date; } export interface CreateUserData { id: string; name: string; email: string; picture: string; } export interface UpdateUserData { name: string; email: string; picture: string; } export class User { private static pool: Pool = getPool(); static async findByGoogleId(googleId: string): Promise<IUser | null> { const client = await this.pool.connect(); try { const result = await client.query( 'SELECT * FROM users WHERE google_id = $1', [googleId] ); return result.rows[0] || null; } catch (error) { console.error('Error finding user by Google ID:', error); throw error; } finally { client.release(); } } static async findByEmail(email: string): Promise<IUser | null> { const client = await this.pool.connect(); try { const result = await client.query( 'SELECT * FROM users WHERE email = $1', [email] ); return result.rows[0] || null; } catch (error) { console.error('Error finding user by email:', error); throw error; } finally { client.release(); } } static async create(userData: CreateUserData): Promise<IUser> { const client = await this.pool.connect(); try { const result = await client.query( \`INSERT INTO users (google_id, name, email, picture) VALUES ($1, $2, $3, $4) RETURNING *\`, [userData.id, userData.name, userData.email, userData.picture] ); return result.rows[0]; } catch (error) { console.error('Error creating user:', error); throw error; } finally { client.release(); } } static async update(googleId: string, userData: UpdateUserData): Promise<IUser | null> { const client = await this.pool.connect(); try { const result = await client.query( \`UPDATE users SET name = $1, email = $2, picture = $3, updated_at = CURRENT_TIMESTAMP WHERE google_id = $4 RETURNING *\`, [userData.name, userData.email, userData.picture, googleId] ); return result.rows[0] || null; } catch (error) { console.error('Error updating user:', error); throw error; } finally { client.release(); } } static async delete(googleId: string): Promise<boolean> { const client = await this.pool.connect(); try { const result = await client.query( 'DELETE FROM users WHERE google_id = $1', [googleId] ); return result.rowCount !== null && result.rowCount > 0; } catch (error) { console.error('Error deleting user:', error); throw error; } finally { client.release(); } } static async findAll(limit: number = 100, offset: number = 0): Promise<IUser[]> { const client = await this.pool.connect(); try { const result = await client.query( 'SELECT * FROM users ORDER BY created_at DESC LIMIT $1 OFFSET $2', [limit, offset] ); return result.rows; } catch (error) { console.error('Error finding all users:', error); throw error; } finally { client.release(); } } static async count(): Promise<number> { const client = await this.pool.connect(); try { const result = await client.query('SELECT COUNT(*) FROM users'); return parseInt(result.rows[0].count); } catch (error) { console.error('Error counting users:', error); throw error; } finally { client.release(); } } static async findById(id: number): Promise<IUser | null> { const client = await this.pool.connect(); try { const result = await client.query( 'SELECT * FROM users WHERE id = $1', [id] ); return result.rows[0] || null; } catch (error) { console.error('Error finding user by ID:', error); throw error; } finally { client.release(); } } }`; await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'src', 'models', 'User.ts'), userModelTs); // Generate database utilities const dbUtilsTs = `import { getPool } from './database'; export class DatabaseUtils { private static pool = getPool(); static async isConnected(): Promise<boolean> { try { const client = await this.pool.connect(); await client.query('SELECT 1'); client.release(); return true; } catch (error) { return false; } } static async ping(): Promise<boolean> { try { const client = await this.pool.connect(); const result = await client.query('SELECT NOW()'); client.release(); return result.rows.length > 0; } catch (error) { return false; } } static async getConnectionInfo(): Promise<{ totalConnections: number; idleConnections: number; waitingClients: number; }> { return { totalConnections: this.pool.totalCount, idleConnections: this.pool.idleCount, waitingClients: this.pool.waitingCount }; } static async executeQuery(query: string, params: any[] = []): Promise<any> { const client = await this.pool.connect(); try { const result = await client.query(query, params); return result; } catch (error) { console.error('Query execution error:', error); throw error; } finally { client.release(); } } static async dropTables(): Promise<void> { if (process.env.NODE_ENV !== 'test') { throw new Error('Tables can only be dropped in test environment'); } const client = await this.pool.connect(); try { await client.query('DROP TABLE IF EXISTS users CASCADE'); console.log('✅ Tables dropped'); } catch (error) { console.error('❌ Error dropping tables:', error); throw error; } finally { client.release(); } } static async clearTables(): Promise<void> { if (process.env.NODE_ENV !== 'test') { throw new Error('Tables can only be cleared in test environment'); } const client = await this.pool.connect(); try { await client.query('TRUNCATE TABLE users RESTART IDENTITY CASCADE'); console.log('✅ Tables cleared'); } catch (error) { console.error('❌ Error clearing tables:', error); throw error; } finally { client.release(); } } }`; await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'src', 'config', 'db-utils.ts'), dbUtilsTs); // Generate migration runner const migrationsTs = `import { getPool } from './database'; import fs from 'fs'; import path from 'path'; export class MigrationRunner { private static pool = getPool(); static async createMigrationsTable(): Promise<void> { const client = await this.pool.connect(); try { await client.query(\` CREATE TABLE IF NOT EXISTS migrations ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, executed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ) \`); } catch (error) { console.error('Error creating migrations table:', error); throw error; } finally { client.release(); } } static async getExecutedMigrations(): Promise<string[]> { const client = await this.pool.connect(); try { const result = await client.query( 'SELECT name FROM migrations ORDER BY id' ); return result.rows.map(row => row.name); } catch (error) { console.error('Error getting executed migrations:', error); throw error; } finally { client.release(); } } static async recordMigration(migrationName: string): Promise<void> { const client = await this.pool.connect(); try { await client.query( 'INSERT INTO migrations (name) VALUES ($1)', [migrationName] ); } catch (error) { console.error('Error recording migration:', error); throw error; } finally { client.release(); } } static async runMigrations(migrationsDir: string = './migrations'): Promise<void> { if (!fs.existsSync(migrationsDir)) { console.log('No migrations directory found'); return; } await this.createMigrationsTable(); const executedMigrations = await this.getExecutedMigrations(); const migrationFiles = fs.readdirSync(migrationsDir) .filter(file => file.endsWith('.sql')) .sort(); for (const file of migrationFiles) { const migrationName = path.basename(file, '.sql'); if (executedMigrations.includes(migrationName)) { console.log(\`⏭️ Skipping already executed migration: \${migrationName}\`); continue; } console.log(\`🔄 Executing migration: \${migrationName}\`); const migrationPath = path.join(migrationsDir, file); const migrationSQL = fs.readFileSync(migrationPath, 'utf8'); const client = await this.pool.connect(); try { await client.query('BEGIN'); await client.query(migrationSQL); await client.query( 'INSERT INTO migrations (name) VALUES ($1)', [migrationName] ); await client.query('COMMIT'); console.log(\`✅ Migration completed: \${migrationName}\`); } catch (error) { await client.query('ROLLBACK'); console.error(\`❌ Migration failed: \${migrationName}\`, error); throw error; } finally { client.release(); } } } }`; await fs_extra_1.default.writeFile(path_1.default.join(backendPath, 'src', 'config', 'migrations.ts'), migrationsTs); } //# sourceMappingURL=postgres-template.js.map