8 changed files with 199 additions and 13 deletions
@ -0,0 +1,20 @@ |
|||||||
|
# Как в HR_TG_Bot: тот же Postgres из Postgres_TG_Bots (docker-compose.dev.yml), |
||||||
|
# отдельная база clinic_tests — таблицы приложения не смешиваются с hr_bot_test. |
||||||
|
# |
||||||
|
# Локально (порт 5432 на хосте, как в Postgres_TG_Bots): |
||||||
|
# DATABASE_URL=postgresql://hr_bot_user:hrbot123@localhost:5432/clinic_tests |
||||||
|
# |
||||||
|
# Backend в Docker рядом с HR: хост — container_name Postgres, порт 5432 внутри сети: |
||||||
|
# DATABASE_URL=postgresql://hr_bot_user:hrbot123@hr_postgres_dev:5432/clinic_tests |
||||||
|
# |
||||||
|
# Базу clinic_tests создают один раз (от суперпользователя контейнера): |
||||||
|
# psql "postgresql://hr_bot_user:hrbot123@localhost:5432/postgres" -c "CREATE DATABASE clinic_tests;" |
||||||
|
# |
||||||
|
# Если DATABASE_URL не задан, используются переменные ниже (устаревший сценарий со своим Postgres на 5433). |
||||||
|
# DB_HOST=localhost |
||||||
|
# DB_PORT=5432 |
||||||
|
# DB_NAME=clinic_tests |
||||||
|
# DB_USER=developer |
||||||
|
# DB_PASSWORD=dev_password |
||||||
|
|
||||||
|
DATABASE_URL=postgresql://hr_bot_user:hrbot123@localhost:5432/clinic_tests |
||||||
@ -0,0 +1,100 @@ |
|||||||
|
/** |
||||||
|
* Database Connection Module |
||||||
|
* PostgreSQL connection pool and utility functions |
||||||
|
*/ |
||||||
|
|
||||||
|
import pg from 'pg'; |
||||||
|
import { getPoolConfig } from './poolConfig.js'; |
||||||
|
|
||||||
|
const { Pool } = pg; |
||||||
|
|
||||||
|
const pool = new Pool(getPoolConfig()); |
||||||
|
|
||||||
|
// Handle pool errors
|
||||||
|
pool.on('error', (err) => { |
||||||
|
console.error('Unexpected pool error:', err.message); |
||||||
|
}); |
||||||
|
|
||||||
|
/** |
||||||
|
* Execute a query with the connection pool |
||||||
|
* @param {string} text - SQL query text |
||||||
|
* @param {Array} params - Query parameters |
||||||
|
* @returns {Promise<pg.QueryResult>} Query result |
||||||
|
*/ |
||||||
|
export async function query(text, params) { |
||||||
|
const start = Date.now(); |
||||||
|
const result = await pool.query(text, params); |
||||||
|
const duration = Date.now() - start; |
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') { |
||||||
|
console.log('Executed query:', { text: text.substring(0, 50), duration, rows: result.rowCount }); |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Execute a query with automatic client release |
||||||
|
* @param {string} text - SQL query text |
||||||
|
* @param {Array} params - Query parameters |
||||||
|
* @returns {Promise<pg.QueryResult>} Query result |
||||||
|
*/ |
||||||
|
export async function queryWithClient(text, params) { |
||||||
|
const client = await pool.connect(); |
||||||
|
try { |
||||||
|
return await client.query(text, params); |
||||||
|
} finally { |
||||||
|
client.release(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Execute a transaction |
||||||
|
* @param {Function} callback - Async function receiving client as parameter |
||||||
|
* @returns {Promise<any>} Transaction result |
||||||
|
*/ |
||||||
|
export async function transaction(callback) { |
||||||
|
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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get a client from the pool |
||||||
|
* @returns {Promise<pg.PoolClient>} Pool client |
||||||
|
*/ |
||||||
|
export async function getClient() { |
||||||
|
return pool.connect(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get pool status information |
||||||
|
* @returns {Object} Pool statistics |
||||||
|
*/ |
||||||
|
export function getPoolStatus() { |
||||||
|
return { |
||||||
|
totalCount: pool.totalCount, |
||||||
|
idleCount: pool.idleCount, |
||||||
|
waitingCount: pool.waitingCount, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Close the connection pool |
||||||
|
* @returns {Promise<void>} |
||||||
|
*/ |
||||||
|
export async function closePool() { |
||||||
|
await pool.end(); |
||||||
|
} |
||||||
|
|
||||||
|
// Default export the pool for direct access if needed
|
||||||
|
export default pool; |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
/** |
||||||
|
* Параметры пула node-postgres, единообразно с HR_TG_Bot / Postgres_TG_Bots: |
||||||
|
* приоритет у `DATABASE_URL` (postgresql://…), иначе DB_HOST, DB_PORT, DB_NAME, …
|
||||||
|
*/ |
||||||
|
|
||||||
|
import dotenv from 'dotenv'; |
||||||
|
|
||||||
|
dotenv.config(); |
||||||
|
|
||||||
|
/** |
||||||
|
* @param {import('pg').PoolConfig} [overrides] |
||||||
|
* @returns {import('pg').PoolConfig} |
||||||
|
*/ |
||||||
|
export function getPoolConfig(overrides = {}) { |
||||||
|
const url = process.env.DATABASE_URL?.trim(); |
||||||
|
if (url) { |
||||||
|
return { |
||||||
|
connectionString: url, |
||||||
|
max: parseInt(process.env.DB_POOL_MAX || '20', 10), |
||||||
|
idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000', 10), |
||||||
|
connectionTimeoutMillis: parseInt( |
||||||
|
process.env.DB_CONNECTION_TIMEOUT || '2000', |
||||||
|
10 |
||||||
|
), |
||||||
|
...overrides, |
||||||
|
}; |
||||||
|
} |
||||||
|
return { |
||||||
|
host: process.env.DB_HOST || 'localhost', |
||||||
|
port: parseInt(process.env.DB_PORT || '5432', 10), |
||||||
|
database: process.env.DB_NAME || 'clinic_tests', |
||||||
|
user: process.env.DB_USER || 'developer', |
||||||
|
password: process.env.DB_PASSWORD || 'dev_password', |
||||||
|
max: parseInt(process.env.DB_POOL_MAX || '20', 10), |
||||||
|
idleTimeoutMillis: parseInt(process.env.DB_IDLE_TIMEOUT || '30000', 10), |
||||||
|
connectionTimeoutMillis: parseInt( |
||||||
|
process.env.DB_CONNECTION_TIMEOUT || '2000', |
||||||
|
10 |
||||||
|
), |
||||||
|
...overrides, |
||||||
|
}; |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
# Опционально: изолированный Postgres на 5433, если не используете общий кластер из |
||||||
|
# ../Postgres_TG_Bots/docker-compose.dev.yml (сеть hr_postgres_dev_net, порт 5432). |
||||||
|
# Основной сценарий: поднять Postgres там, создать БД clinic_tests, в backend/.env задать DATABASE_URL |
||||||
|
# (см. backend/.env.example) — аналогично HR_TG_Bot (DATABASE_URL к hr_postgres_dev или localhost:5432). |
||||||
|
|
||||||
|
services: |
||||||
|
postgres: |
||||||
|
image: postgres:15 |
||||||
|
environment: |
||||||
|
POSTGRES_DB: clinic_tests |
||||||
|
POSTGRES_USER: developer |
||||||
|
POSTGRES_PASSWORD: dev_password |
||||||
|
ports: |
||||||
|
- "5433:5432" |
||||||
|
volumes: |
||||||
|
- postgres_data:/var/lib/postgresql/data |
||||||
|
|
||||||
|
volumes: |
||||||
|
postgres_data: |
||||||
Loading…
Reference in new issue