Initial commit

This commit is contained in:
2025-06-22 00:01:22 +03:00
parent fd70166cf6
commit 033b80bfad
153 changed files with 26874 additions and 1 deletions

72
lib/auth.ts Normal file
View 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
View 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
View 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
}

View 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
View 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
View 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
View 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
View 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 }