diff --git a/README.md b/README.md index 9511ea4..c955644 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,19 @@ ## Установка и запуск -Инструкции по установке и запуску приложения будут добавлены после выбора технологического стека. +### База данных (как в HR_TG_Bot / Postgres_TG_Bots) + +Используется **тот же** экземпляр PostgreSQL, что и в [Postgres_TG_Bots](../Postgres_TG_Bots) (`docker-compose.dev.yml`, контейнер `hr_postgres_dev`, учётка `hr_bot_user` / сеть `hr_postgres_dev_net` — см. [HR_TG_Bot docker-compose](../HR_TG_Bot/docker-compose.dev.yml)). + +Схема приложения (таблицы `users`, `tests`, `departments`, …) **не** совмещается с БД `hr_bot_test` — для TestingWebApp заведена отдельная база **`clinic_tests`**. + +1. Поднять Postgres из `Postgres_TG_Bots` (и при необходимости внешнюю сеть: `docker network create hr_postgres_dev_net` — как в compose этих репозиториев). +2. Один раз создать базу: + `psql "postgresql://hr_bot_user:hrbot123@localhost:5432/postgres" -c "CREATE DATABASE clinic_tests;"` +3. Скопировать `backend/.env.example` в `backend/.env`, при необходимости поправить `DATABASE_URL` (внутри Docker кластера — хост `hr_postgres_dev`, порт `5432`). +4. Миграции: из каталога `backend/`: `npm run migrate`, затем `npm run start` (и фронт из `frontend/` — `npm run dev`). + +Старый вариант **только** с локальным Postgres на порту 5433: корневой `docker compose up -d` в TestingWebApp и в `.env` порт `5433` (или отдельные `DB_*` без `DATABASE_URL`) — оставлен для разработки без общего `Postgres_TG_Bots`. --- diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ebe1a27 --- /dev/null +++ b/backend/.env.example @@ -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 diff --git a/backend/package.json b/backend/package.json index f0d936b..0d3c7ef 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "start": "node src/index.js", "dev": "node --watch src/index.js", "test": "node --test src/services/testChainService.test.js", + "migrate": "node src/db/migrate.js", "lint": "eslint src/", "lint:fix": "eslint src/ --fix", "format": "prettier --write src/" diff --git a/backend/src/db/db.js b/backend/src/db/db.js new file mode 100644 index 0000000..e572684 --- /dev/null +++ b/backend/src/db/db.js @@ -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} 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} 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} 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} 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} + */ +export async function closePool() { + await pool.end(); +} + +// Default export the pool for direct access if needed +export default pool; diff --git a/backend/src/db/migrate.js b/backend/src/db/migrate.js index b5af4d4..e564d32 100644 --- a/backend/src/db/migrate.js +++ b/backend/src/db/migrate.js @@ -6,18 +6,10 @@ import { readFileSync, readdirSync } from 'fs'; import { join } from 'path'; import pg from 'pg'; +import { getPoolConfig } from './poolConfig.js'; const { Pool } = pg; -// Database configuration -const dbConfig = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5433', 10), - database: process.env.DB_NAME || 'clinic_tests', - user: process.env.DB_USER || 'developer', - password: process.env.DB_PASSWORD || 'dev_password', -}; - const MIGRATIONS_DIR = join(process.cwd(), 'src', 'db', 'migrations'); /** @@ -81,7 +73,7 @@ async function executeMigration(pool, filename) { * Main migration function */ async function migrate() { - const pool = new Pool(dbConfig); + const pool = new Pool(getPoolConfig()); try { console.log('Connecting to database...'); diff --git a/backend/src/db/poolConfig.js b/backend/src/db/poolConfig.js new file mode 100644 index 0000000..997a3f8 --- /dev/null +++ b/backend/src/db/poolConfig.js @@ -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, + }; +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b6520e7 --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/docs/шаги/01-project-setup.md b/docs/шаги/01-project-setup.md index 90665c5..47e3b41 100644 --- a/docs/шаги/01-project-setup.md +++ b/docs/шаги/01-project-setup.md @@ -26,8 +26,8 @@ ### 1.3. Настройка окружения -- Docker Compose для PostgreSQL -- Переменные окружения (.env) +- PostgreSQL: общий кластер с [Postgres_TG_Bots](../../../Postgres_TG_Bots) / [HR_TG_Bot](../../../HR_TG_Bot) (`DATABASE_URL` в `backend/.env`, отдельная БД `clinic_tests` — см. [README — Установка и запуск](../../README.md#установка-и-запуск)). Опционально: локальный Postgres только для TestingWebApp — корневой `docker-compose.yml` (порт 5433). +- Переменные окружения (`.env` по образцу `backend/.env.example`) - Настройка линтеров и форматтеров ### 1.4. Базовая структура API