feat: Add CHORUS teaser website with mobile-responsive design
- Created complete Next.js 15 teaser website with CHORUS brand styling - Implemented mobile-responsive 3D logo (128px mobile, 512px desktop) - Added proper Exo font loading via Next.js Google Fonts for iOS/Chrome compatibility - Built comprehensive early access form with GDPR compliance and rate limiting - Integrated PostgreSQL database with complete schema for lead capture - Added scroll indicators that auto-hide when scrolling begins - Optimized mobile modal forms with proper scrolling and submit button access - Deployed via Docker Swarm with Traefik SSL termination at chorus.services - Includes database migrations, consent tracking, and email notifications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
86
modules/teaser/lib/db.ts
Normal file
86
modules/teaser/lib/db.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Pool, PoolClient } from 'pg'
|
||||
|
||||
// Database connection configuration
|
||||
const dbConfig = {
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
ssl: false, // No SSL needed for internal Docker network
|
||||
max: 20,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000, // Increased timeout for Docker networking
|
||||
}
|
||||
|
||||
// Global connection pool
|
||||
let globalPool: Pool | undefined
|
||||
|
||||
// Initialize database pool
|
||||
function initializePool(): Pool {
|
||||
if (!globalPool) {
|
||||
globalPool = new Pool(dbConfig)
|
||||
|
||||
// Handle pool errors
|
||||
globalPool.on('error', (err) => {
|
||||
console.error('Unexpected error on idle client', err)
|
||||
})
|
||||
}
|
||||
|
||||
return globalPool
|
||||
}
|
||||
|
||||
// Get database connection pool
|
||||
export function getDbPool(): Pool {
|
||||
return initializePool()
|
||||
}
|
||||
|
||||
// Execute a query with automatic connection handling
|
||||
export async function query<T = any>(text: string, params?: any[]): Promise<T[]> {
|
||||
const pool = getDbPool()
|
||||
|
||||
try {
|
||||
const result = await pool.query(text, params)
|
||||
return result.rows
|
||||
} catch (error) {
|
||||
console.error('Database query error:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a transaction
|
||||
export async function transaction<T>(
|
||||
callback: (client: PoolClient) => Promise<T>
|
||||
): Promise<T> {
|
||||
const pool = getDbPool()
|
||||
const client = await pool.connect()
|
||||
|
||||
try {
|
||||
await client.query('BEGIN')
|
||||
const result = await callback(client)
|
||||
await client.query('COMMIT')
|
||||
return result
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK')
|
||||
throw error
|
||||
} finally {
|
||||
client.release()
|
||||
}
|
||||
}
|
||||
|
||||
// Health check function
|
||||
export async function healthCheck(): Promise<{ status: string; timestamp: string }> {
|
||||
try {
|
||||
const result = await query('SELECT NOW() as timestamp')
|
||||
return {
|
||||
status: 'healthy',
|
||||
timestamp: result[0].timestamp
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Database health check failed: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Close the pool (useful for cleanup)
|
||||
export async function closePool(): Promise<void> {
|
||||
if (globalPool) {
|
||||
await globalPool.end()
|
||||
globalPool = undefined
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user