Files
ipwhitelist-app/v2/src/crud/index.js
T

85 lines
4.9 KiB
JavaScript

// ═══════════════════════════════════════════════════════════════════════════════
// V2 — CRUD API-слой
//
// ЭТО: чистые функции — API между фронтендами и БД.
// НЕ: Express, HTTP, req/res, сессии.
//
// ЗАЧЕМ:
// 1. Фронтенды (user/, admin/, test/) не знают про SQL, транзакции, companyId.
// 2. Валидация вызывается ОДИН раз здесь, не дублируется в каждом фронтенде.
// 3. clientId (W-номер) → companyId (внутренний ID БД) резолвится здесь.
// 4. Каждую функцию можно тестировать изолированно, без Express.
//
// КОНТРАКТ:
// list(clientId) → { entries, used, limit }
// add(clientId, cidr, ...) → { entry, wasNormalized }
// edit(entryId, clientId, ...) → { entry, wasNormalized }
// remove(entryId, clientId, ...) → void
//
// Вход: clientId — W-номер компании (строка вида "WZ10001").
// email — почта юзера (для аудита).
// impBy — почта админа при имперсонации (null если нет).
//
// Ошибки: throw — валидация, лимит, дубликат, пересечение.
// ═══════════════════════════════════════════════════════════════════════════════
const q = require('../db/queries');
const { validate } = require('../validators');
// ── resolve(clientId) → company (внутренний хелпер) ──────────────────────────
// Атомарный upsert: если компании с таким W-номером нет — создаст.
// Возвращает { id, client_id, name, custom_limit }.
// Вызывается из list/add/edit/remove перед каждой операцией.
async function resolve(clientId) {
return q.getOrCreateCompany(clientId, clientId);
}
// ── list(clientId, includeDeleted?) → { entries, used, limit } ──────────────
// Всегда возвращает лимит и счётчик активных записей.
// used — только неудалённые (deleted_at IS NULL).
async function list(clientId, includeDeleted = false) {
const co = await resolve(clientId);
const entries = await q.listEntries(co.id, includeDeleted);
const limit = await q.getLimit(co);
const used = entries.filter(e => !e.deleted_at).length;
return { entries, used, limit };
}
// ── add(clientId, cidr, comment, email, impBy) → { entry, wasNormalized } ───
// ПОРЯДОК:
// 1. validate(rawCidr) — проверка ТЗ (маска, диапазоны, нормализация).
// 2. resolve(clientId) — получить/создать компанию.
// 3. db.createEntry() — транзакция: лимит, дубликаты, INSERT + аудит.
// Если validate() бросает — до БД не доходим.
async function add(clientId, cidr, comment, email, impBy) {
const { cidr: validated, wasNormalized } = validate(cidr);
const co = await resolve(clientId);
return q.createEntry(co.id, validated, comment, email, impBy, wasNormalized);
}
// ── edit(entryId, clientId, cidr, comment, email, impBy) → { entry, wasNormalized }
// Аналогично add, но для существующей записи.
// entryId — id из v2_entries (число).
async function edit(entryId, clientId, cidr, comment, email, impBy) {
const { cidr: validated, wasNormalized } = validate(cidr);
const co = await resolve(clientId);
return q.updateEntry(entryId, co.id, validated, comment, email, impBy, wasNormalized);
}
// ── remove(entryId, clientId, email, impBy) → void ──────────────────────────
// Soft delete: ставит deleted_at, записывает аудит.
// Запись остаётся в БД, но не показывается в list().
async function remove(entryId, clientId, email, impBy) {
const co = await resolve(clientId);
return q.deleteEntry(entryId, co.id, email, impBy);
}
// ── restore(entryId, clientId, email, impBy) → void ─────────────────────────
// Восстановление удалённой записи.
async function restore(entryId, clientId, email, impBy) {
const co = await resolve(clientId);
return q.restoreEntry(entryId, co.id, email, impBy);
}
module.exports = { list, add, edit, remove, restore };