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
JavaScript
"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',
[]
);
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',
[]
);
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 *\`,
[]
);
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',
[]
);
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',
[]
);
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',
[]
);
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)',
[]
);
} 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)',
[]
);
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