Initial commit
This commit is contained in:
72
lib/auth.ts
Normal file
72
lib/auth.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import bcrypt from "bcryptjs"
|
||||
|
||||
export function validateIsraeliID(id: string): boolean {
|
||||
if (!/^\d{9}$/.test(id)) return false
|
||||
|
||||
// Luhn algorithm for Israeli ID
|
||||
let sum = 0
|
||||
for (let i = 0; i < 9; i++) {
|
||||
let digit = Number.parseInt(id[i])
|
||||
if (i % 2 === 1) {
|
||||
digit *= 2
|
||||
if (digit > 9) digit -= 9
|
||||
}
|
||||
sum += digit
|
||||
}
|
||||
return sum % 10 === 0
|
||||
}
|
||||
|
||||
export function generateValidIsraeliID(): string {
|
||||
// Generate 8 random digits
|
||||
let id = ""
|
||||
for (let i = 0; i < 8; i++) {
|
||||
id += Math.floor(Math.random() * 10).toString()
|
||||
}
|
||||
|
||||
// Calculate the check digit using Luhn algorithm
|
||||
let sum = 0
|
||||
for (let i = 0; i < 8; i++) {
|
||||
let digit = Number.parseInt(id[i])
|
||||
if (i % 2 === 1) {
|
||||
digit *= 2
|
||||
if (digit > 9) digit -= 9
|
||||
}
|
||||
sum += digit
|
||||
}
|
||||
|
||||
// Calculate check digit
|
||||
const checkDigit = (10 - (sum % 10)) % 10
|
||||
id += checkDigit.toString()
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
export async function generateUniqueIsraeliID(): Promise<string> {
|
||||
const { safeQuery } = await import("./database")
|
||||
|
||||
let attempts = 0
|
||||
const maxAttempts = 100
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
const newId = generateValidIsraeliID()
|
||||
|
||||
// Check if this ID already exists
|
||||
const existingUsers = (await safeQuery("SELECT national_id FROM users WHERE national_id = ?", [newId])) as any[]
|
||||
|
||||
if (existingUsers.length === 0) {
|
||||
return newId
|
||||
}
|
||||
|
||||
attempts++
|
||||
}
|
||||
|
||||
throw new Error("Unable to generate unique ID after maximum attempts")
|
||||
}
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return bcrypt.hash(password, 12)
|
||||
}
|
||||
|
||||
export async function verifyPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash)
|
||||
}
|
||||
45
lib/connection-monitor.ts
Normal file
45
lib/connection-monitor.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { getPoolStats } from "./database"
|
||||
|
||||
let monitoringInterval: NodeJS.Timeout | null = null
|
||||
|
||||
export function startConnectionMonitoring() {
|
||||
if (monitoringInterval) {
|
||||
return // Already monitoring
|
||||
}
|
||||
|
||||
console.log("Starting database connection monitoring...")
|
||||
|
||||
monitoringInterval = setInterval(() => {
|
||||
const stats = getPoolStats()
|
||||
if (stats) {
|
||||
const utilizationRate = (stats.totalConnections - stats.freeConnections) / stats.connectionLimit
|
||||
|
||||
console.log(
|
||||
`DB Pool Stats: ${stats.totalConnections}/${stats.connectionLimit} total, ${stats.freeConnections} free, ${stats.acquiringConnections} acquiring (${Math.round(utilizationRate * 100)}% utilization)`,
|
||||
)
|
||||
|
||||
// Warn if utilization is high
|
||||
if (utilizationRate > 0.8) {
|
||||
console.warn("⚠️ High database connection utilization detected!")
|
||||
}
|
||||
|
||||
// Warn if many connections are waiting
|
||||
if (stats.acquiringConnections > 3) {
|
||||
console.warn("⚠️ Many connections waiting for pool!")
|
||||
}
|
||||
}
|
||||
}, 30000) // Every 30 seconds
|
||||
}
|
||||
|
||||
export function stopConnectionMonitoring() {
|
||||
if (monitoringInterval) {
|
||||
clearInterval(monitoringInterval)
|
||||
monitoringInterval = null
|
||||
console.log("Stopped database connection monitoring")
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-start monitoring in production
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
startConnectionMonitoring()
|
||||
}
|
||||
100
lib/database-cleanup.ts
Normal file
100
lib/database-cleanup.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { executeQuery, getPoolStats, closePool } from "./database"
|
||||
import { startConnectionMonitoring } from "./connection-monitor"
|
||||
|
||||
// Clean up old admin actions (keep only last 30 days)
|
||||
export async function cleanupOldActions() {
|
||||
try {
|
||||
const result = await executeQuery("DELETE FROM admin_actions WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY)")
|
||||
console.log(`Cleaned up old admin actions: ${(result as any).affectedRows} rows deleted`)
|
||||
return (result as any).affectedRows
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up old actions:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Reset inactive user sessions (users who haven't updated status in 24 hours)
|
||||
export async function resetInactiveSessions() {
|
||||
try {
|
||||
const result = await executeQuery(
|
||||
"UPDATE users SET in_shelter = NULL WHERE last_updated < DATE_SUB(NOW(), INTERVAL 24 HOUR) AND in_shelter IS NOT NULL",
|
||||
)
|
||||
console.log(`Reset inactive sessions: ${(result as any).affectedRows} users reset`)
|
||||
return (result as any).affectedRows
|
||||
} catch (error) {
|
||||
console.error("Error resetting inactive sessions:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Get database statistics
|
||||
export async function getDatabaseStats() {
|
||||
try {
|
||||
const [userStats] = (await executeQuery(`
|
||||
SELECT
|
||||
COUNT(*) as total_users,
|
||||
COUNT(CASE WHEN in_shelter IS NOT NULL THEN 1 END) as active_users,
|
||||
COUNT(CASE WHEN is_admin = TRUE THEN 1 END) as admin_users,
|
||||
COUNT(CASE WHEN last_updated > DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN 1 END) as recent_activity
|
||||
FROM users
|
||||
`)) as any[]
|
||||
|
||||
const [actionStats] = (await executeQuery(`
|
||||
SELECT
|
||||
COUNT(*) as total_actions,
|
||||
COUNT(CASE WHEN timestamp > DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 1 END) as actions_24h,
|
||||
COUNT(CASE WHEN action_type = 'reset_all' THEN 1 END) as reset_actions
|
||||
FROM admin_actions
|
||||
`)) as any[]
|
||||
|
||||
const poolStats = getPoolStats()
|
||||
|
||||
return {
|
||||
users: userStats,
|
||||
actions: actionStats,
|
||||
pool: poolStats,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting database stats:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful shutdown with connection cleanup
|
||||
export async function gracefulShutdown() {
|
||||
console.log("Starting graceful database shutdown...")
|
||||
|
||||
try {
|
||||
// Clean up old data
|
||||
await cleanupOldActions()
|
||||
|
||||
// Close all connections
|
||||
await closePool()
|
||||
|
||||
console.log("Database shutdown completed successfully")
|
||||
} catch (error) {
|
||||
console.error("Error during database shutdown:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule periodic cleanup and start monitoring
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
// Start connection monitoring
|
||||
startConnectionMonitoring()
|
||||
|
||||
setInterval(
|
||||
async () => {
|
||||
try {
|
||||
console.log("Running scheduled database cleanup...")
|
||||
await cleanupOldActions()
|
||||
|
||||
const stats = await getDatabaseStats()
|
||||
console.log("Database stats:", stats)
|
||||
} catch (error) {
|
||||
console.error("Scheduled cleanup failed:", error)
|
||||
}
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
) // Every hour
|
||||
}
|
||||
96
lib/database-cleanup.ts.bkp
Normal file
96
lib/database-cleanup.ts.bkp
Normal file
@@ -0,0 +1,96 @@
|
||||
import { executeQuery, getPoolStats, closePool } from "./database"
|
||||
|
||||
// Clean up old admin actions (keep only last 30 days)
|
||||
export async function cleanupOldActions() {
|
||||
try {
|
||||
const result = await executeQuery("DELETE FROM admin_actions WHERE timestamp < DATE_SUB(NOW(), INTERVAL 30 DAY)")
|
||||
console.log(`Cleaned up old admin actions: ${(result as any).affectedRows} rows deleted`)
|
||||
return (result as any).affectedRows
|
||||
} catch (error) {
|
||||
console.error("Error cleaning up old actions:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Reset inactive user sessions (users who haven't updated status in 24 hours)
|
||||
export async function resetInactiveSessions() {
|
||||
try {
|
||||
const result = await executeQuery(
|
||||
"UPDATE users SET in_shelter = NULL WHERE last_updated < DATE_SUB(NOW(), INTERVAL 24 HOUR) AND in_shelter IS NOT NULL",
|
||||
)
|
||||
console.log(`Reset inactive sessions: ${(result as any).affectedRows} users reset`)
|
||||
return (result as any).affectedRows
|
||||
} catch (error) {
|
||||
console.error("Error resetting inactive sessions:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Get database statistics
|
||||
export async function getDatabaseStats() {
|
||||
try {
|
||||
const [userStats] = (await executeQuery(`
|
||||
SELECT
|
||||
COUNT(*) as total_users,
|
||||
COUNT(CASE WHEN in_shelter IS NOT NULL THEN 1 END) as active_users,
|
||||
COUNT(CASE WHEN is_admin = TRUE THEN 1 END) as admin_users,
|
||||
COUNT(CASE WHEN last_updated > DATE_SUB(NOW(), INTERVAL 1 HOUR) THEN 1 END) as recent_activity
|
||||
FROM users
|
||||
`)) as any[]
|
||||
|
||||
const [actionStats] = (await executeQuery(`
|
||||
SELECT
|
||||
COUNT(*) as total_actions,
|
||||
COUNT(CASE WHEN timestamp > DATE_SUB(NOW(), INTERVAL 24 HOUR) THEN 1 END) as actions_24h,
|
||||
COUNT(CASE WHEN action_type = 'reset_all' THEN 1 END) as reset_actions
|
||||
FROM admin_actions
|
||||
`)) as any[]
|
||||
|
||||
const poolStats = getPoolStats()
|
||||
|
||||
return {
|
||||
users: userStats,
|
||||
actions: actionStats,
|
||||
pool: poolStats,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error getting database stats:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Graceful shutdown with connection cleanup
|
||||
export async function gracefulShutdown() {
|
||||
console.log("Starting graceful database shutdown...")
|
||||
|
||||
try {
|
||||
// Clean up old data
|
||||
await cleanupOldActions()
|
||||
|
||||
// Close all connections
|
||||
await closePool()
|
||||
|
||||
console.log("Database shutdown completed successfully")
|
||||
} catch (error) {
|
||||
console.error("Error during database shutdown:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Schedule periodic cleanup (run every hour)
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
setInterval(
|
||||
async () => {
|
||||
try {
|
||||
console.log("Running scheduled database cleanup...")
|
||||
await cleanupOldActions()
|
||||
|
||||
const stats = await getDatabaseStats()
|
||||
console.log("Database stats:", stats)
|
||||
} catch (error) {
|
||||
console.error("Scheduled cleanup failed:", error)
|
||||
}
|
||||
},
|
||||
60 * 60 * 1000,
|
||||
) // Every hour
|
||||
}
|
||||
242
lib/database.ts
Normal file
242
lib/database.ts
Normal file
@@ -0,0 +1,242 @@
|
||||
import mysql from "mysql2/promise"
|
||||
import config from "../config.json"
|
||||
|
||||
// Connection pool configuration
|
||||
const poolConfig = {
|
||||
host: config.database.host,
|
||||
user: config.database.user,
|
||||
password: config.database.password,
|
||||
database: config.database.database,
|
||||
charset: "utf8mb4",
|
||||
connectionLimit: 5, // Reduced from 10
|
||||
acquireTimeout: 30000, // Reduced from 60 seconds
|
||||
timeout: 30000, // Reduced from 60 seconds
|
||||
reconnect: true,
|
||||
idleTimeout: 180000, // Reduced to 3 minutes
|
||||
maxIdle: 2, // Reduced from 5
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 0,
|
||||
// Add these to prevent excessive connections
|
||||
queueLimit: 0,
|
||||
dateStrings: false,
|
||||
debug: false,
|
||||
}
|
||||
|
||||
let pool: mysql.Pool | null = null
|
||||
|
||||
// Create connection pool
|
||||
export function createPool() {
|
||||
if (!pool) {
|
||||
try {
|
||||
pool = mysql.createPool(poolConfig)
|
||||
console.log("Database connection pool created successfully")
|
||||
|
||||
// Handle pool events
|
||||
pool.on("connection", (connection) => {
|
||||
console.log(`New database connection established as id ${connection.threadId}`)
|
||||
})
|
||||
|
||||
pool.on("error", (err) => {
|
||||
console.error("Database pool error:", err)
|
||||
// Don't recreate pool automatically - let it handle reconnections
|
||||
if (err.code === "PROTOCOL_CONNECTION_LOST") {
|
||||
console.log("Connection lost, pool will handle reconnection automatically")
|
||||
}
|
||||
})
|
||||
|
||||
pool.on("acquire", (connection) => {
|
||||
console.log(`Connection ${connection.threadId} acquired from pool`)
|
||||
})
|
||||
|
||||
pool.on("release", (connection) => {
|
||||
console.log(`Connection ${connection.threadId} released back to pool`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to create database pool:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
// Get connection from pool
|
||||
export async function getConnection(): Promise<mysql.PoolConnection> {
|
||||
try {
|
||||
const connectionPool = createPool()
|
||||
const connection = await connectionPool.getConnection()
|
||||
|
||||
// Don't set session timeouts - let pool handle this
|
||||
return connection
|
||||
} catch (error) {
|
||||
console.error("Failed to get database connection:", error)
|
||||
throw new Error("Database connection failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Execute query with automatic connection management
|
||||
export async function executeQuery(query: string, params: any[] = []) {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
const [results] = await connection.execute(query, params)
|
||||
return results
|
||||
} catch (error) {
|
||||
console.error("Query execution failed:", error)
|
||||
console.error("Query:", query)
|
||||
console.error("Params:", params)
|
||||
throw error
|
||||
} finally {
|
||||
// Always release connection back to pool
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safe query execution with automatic parameter binding and connection management
|
||||
export async function safeQuery(query: string, params: any[] = []) {
|
||||
try {
|
||||
return await executeQuery(query, params)
|
||||
} catch (error) {
|
||||
console.error("Database query error:", error)
|
||||
throw new Error("Database operation failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Execute transaction with automatic connection management
|
||||
export async function executeTransaction(queries: Array<{ query: string; params?: any[] }>) {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
|
||||
// Start transaction
|
||||
await connection.beginTransaction()
|
||||
|
||||
const results = []
|
||||
|
||||
// Execute all queries in transaction
|
||||
for (const { query, params = [] } of queries) {
|
||||
const [result] = await connection.execute(query, params)
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
await connection.commit()
|
||||
|
||||
return results
|
||||
} catch (error) {
|
||||
// Rollback transaction on error
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.rollback()
|
||||
} catch (rollbackError) {
|
||||
console.error("Transaction rollback failed:", rollbackError)
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Transaction failed:", error)
|
||||
throw error
|
||||
} finally {
|
||||
// Always release connection back to pool
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test database connection and pool health
|
||||
export async function testConnection() {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
await connection.ping()
|
||||
console.log("Database connection test successful")
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("Database connection test failed:", error)
|
||||
return false
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get pool statistics
|
||||
export function getPoolStats() {
|
||||
if (!pool) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
totalConnections: pool.pool._allConnections.length,
|
||||
freeConnections: pool.pool._freeConnections.length,
|
||||
acquiringConnections: pool.pool._acquiringConnections.length,
|
||||
connectionLimit: poolConfig.connectionLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// Close all connections in pool (for graceful shutdown)
|
||||
export async function closePool() {
|
||||
if (pool) {
|
||||
try {
|
||||
await pool.end()
|
||||
pool = null
|
||||
console.log("Database pool closed successfully")
|
||||
} catch (error) {
|
||||
console.error("Error closing database pool:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Health check for monitoring
|
||||
export async function healthCheck() {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
connection = await getConnection()
|
||||
|
||||
// Test basic query
|
||||
const [result] = await connection.execute("SELECT 1 as health_check")
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
const stats = getPoolStats()
|
||||
|
||||
return {
|
||||
status: "healthy",
|
||||
responseTime,
|
||||
poolStats: stats,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "unhealthy",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize pool on module load
|
||||
// createPool()
|
||||
|
||||
// Graceful shutdown handler
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("Received SIGINT, closing database pool...")
|
||||
await closePool()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("Received SIGTERM, closing database pool...")
|
||||
await closePool()
|
||||
process.exit(0)
|
||||
})
|
||||
234
lib/database.ts.bkp
Normal file
234
lib/database.ts.bkp
Normal file
@@ -0,0 +1,234 @@
|
||||
import mysql from "mysql2/promise"
|
||||
import config from "../config.json"
|
||||
|
||||
// Connection pool configuration
|
||||
const poolConfig = {
|
||||
host: config.database.host,
|
||||
user: config.database.user,
|
||||
password: config.database.password,
|
||||
database: config.database.database,
|
||||
charset: "utf8mb4",
|
||||
connectionLimit: 10, // Maximum number of connections in pool
|
||||
acquireTimeout: 60000, // Maximum time to wait for a connection (60 seconds)
|
||||
timeout: 60000, // Maximum time for a query (60 seconds)
|
||||
reconnect: true,
|
||||
idleTimeout: 300000, // Close idle connections after 5 minutes
|
||||
maxIdle: 5, // Maximum idle connections
|
||||
enableKeepAlive: true,
|
||||
keepAliveInitialDelay: 0,
|
||||
}
|
||||
|
||||
let pool: mysql.Pool | null = null
|
||||
|
||||
// Create connection pool
|
||||
export function createPool() {
|
||||
if (!pool) {
|
||||
try {
|
||||
pool = mysql.createPool(poolConfig)
|
||||
console.log("Database connection pool created successfully")
|
||||
|
||||
// Handle pool events
|
||||
pool.on("connection", (connection) => {
|
||||
console.log(`New database connection established as id ${connection.threadId}`)
|
||||
})
|
||||
|
||||
pool.on("error", (err) => {
|
||||
console.error("Database pool error:", err)
|
||||
if (err.code === "PROTOCOL_CONNECTION_LOST") {
|
||||
console.log("Recreating database pool...")
|
||||
pool = null
|
||||
createPool()
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Failed to create database pool:", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
return pool
|
||||
}
|
||||
|
||||
// Get connection from pool
|
||||
export async function getConnection(): Promise<mysql.PoolConnection> {
|
||||
try {
|
||||
const connectionPool = createPool()
|
||||
const connection = await connectionPool.getConnection()
|
||||
|
||||
// Set connection timeout
|
||||
await connection.query("SET SESSION wait_timeout = 300") // 5 minutes
|
||||
await connection.query("SET SESSION interactive_timeout = 300")
|
||||
|
||||
return connection
|
||||
} catch (error) {
|
||||
console.error("Failed to get database connection:", error)
|
||||
throw new Error("Database connection failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Execute query with automatic connection management
|
||||
export async function executeQuery(query: string, params: any[] = []) {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
const [results] = await connection.execute(query, params)
|
||||
return results
|
||||
} catch (error) {
|
||||
console.error("Query execution failed:", error)
|
||||
console.error("Query:", query)
|
||||
console.error("Params:", params)
|
||||
throw error
|
||||
} finally {
|
||||
// Always release connection back to pool
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Safe query execution with automatic parameter binding and connection management
|
||||
export async function safeQuery(query: string, params: any[] = []) {
|
||||
try {
|
||||
return await executeQuery(query, params)
|
||||
} catch (error) {
|
||||
console.error("Database query error:", error)
|
||||
throw new Error("Database operation failed")
|
||||
}
|
||||
}
|
||||
|
||||
// Execute transaction with automatic connection management
|
||||
export async function executeTransaction(queries: Array<{ query: string; params?: any[] }>) {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
|
||||
// Start transaction
|
||||
await connection.beginTransaction()
|
||||
|
||||
const results = []
|
||||
|
||||
// Execute all queries in transaction
|
||||
for (const { query, params = [] } of queries) {
|
||||
const [result] = await connection.execute(query, params)
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
// Commit transaction
|
||||
await connection.commit()
|
||||
|
||||
return results
|
||||
} catch (error) {
|
||||
// Rollback transaction on error
|
||||
if (connection) {
|
||||
try {
|
||||
await connection.rollback()
|
||||
} catch (rollbackError) {
|
||||
console.error("Transaction rollback failed:", rollbackError)
|
||||
}
|
||||
}
|
||||
|
||||
console.error("Transaction failed:", error)
|
||||
throw error
|
||||
} finally {
|
||||
// Always release connection back to pool
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test database connection and pool health
|
||||
export async function testConnection() {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
connection = await getConnection()
|
||||
await connection.ping()
|
||||
console.log("Database connection test successful")
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error("Database connection test failed:", error)
|
||||
return false
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get pool statistics
|
||||
export function getPoolStats() {
|
||||
if (!pool) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
totalConnections: pool.pool._allConnections.length,
|
||||
freeConnections: pool.pool._freeConnections.length,
|
||||
acquiringConnections: pool.pool._acquiringConnections.length,
|
||||
connectionLimit: poolConfig.connectionLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// Close all connections in pool (for graceful shutdown)
|
||||
export async function closePool() {
|
||||
if (pool) {
|
||||
try {
|
||||
await pool.end()
|
||||
pool = null
|
||||
console.log("Database pool closed successfully")
|
||||
} catch (error) {
|
||||
console.error("Error closing database pool:", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Health check for monitoring
|
||||
export async function healthCheck() {
|
||||
let connection: mysql.PoolConnection | null = null
|
||||
|
||||
try {
|
||||
const startTime = Date.now()
|
||||
connection = await getConnection()
|
||||
|
||||
// Test basic query
|
||||
const [result] = await connection.execute("SELECT 1 as health_check")
|
||||
const responseTime = Date.now() - startTime
|
||||
|
||||
const stats = getPoolStats()
|
||||
|
||||
return {
|
||||
status: "healthy",
|
||||
responseTime,
|
||||
poolStats: stats,
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
status: "unhealthy",
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize pool on module load
|
||||
createPool()
|
||||
|
||||
// Graceful shutdown handler
|
||||
process.on("SIGINT", async () => {
|
||||
console.log("Received SIGINT, closing database pool...")
|
||||
await closePool()
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
process.on("SIGTERM", async () => {
|
||||
console.log("Received SIGTERM, closing database pool...")
|
||||
await closePool()
|
||||
process.exit(0)
|
||||
})
|
||||
6
lib/utils.ts
Normal file
6
lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
57
lib/websocket.ts
Normal file
57
lib/websocket.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { WebSocketServer } from "ws"
|
||||
|
||||
let wss: WebSocketServer | null = null
|
||||
|
||||
export function initWebSocketServer(server: any) {
|
||||
if (wss) return wss
|
||||
|
||||
wss = new WebSocketServer({ server })
|
||||
|
||||
wss.on("connection", (ws) => {
|
||||
console.log("Admin connected to WebSocket")
|
||||
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
const data = JSON.parse(message.toString())
|
||||
if (data.type === "ping") {
|
||||
ws.send(JSON.stringify({ type: "pong" }))
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("WebSocket message error:", err)
|
||||
}
|
||||
})
|
||||
|
||||
ws.on("close", () => {
|
||||
console.log("Admin disconnected from WebSocket")
|
||||
})
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error("WebSocket error:", error)
|
||||
})
|
||||
})
|
||||
|
||||
return wss
|
||||
}
|
||||
|
||||
export function broadcastUpdate(data: any) {
|
||||
if (!wss) return
|
||||
|
||||
const message = JSON.stringify({
|
||||
type: "update",
|
||||
data,
|
||||
timestamp: new Date().toISOString(),
|
||||
})
|
||||
|
||||
wss.clients.forEach((client) => {
|
||||
if (client.readyState === 1) {
|
||||
// WebSocket.OPEN
|
||||
try {
|
||||
client.send(message)
|
||||
} catch (err) {
|
||||
console.error("Error sending WebSocket message:", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export { wss }
|
||||
Reference in New Issue
Block a user