refactor(repo): переход на pnpm monorepo с shadcn/ui и Fastify+Drizzle

Frontend:
- apps/web (Vite+TS, TanStack Router/Query, shadcn/ui @cfdm/ui base-nova)
- 10 страниц в routes/_auth/, Recharts через shadcn Chart, lucide-react
- формы на RHF + Zod (FormSheet/FormField)
- удалены Tabler, Chart.js, react-router-dom

Backend (параллельный трек):
- apps/api (Fastify 5 + Drizzle + better-sqlite3)
- packages/db: Drizzle-схема и repositories по сущностям
- packages/shared: Zod-контракты
- роуты с валидацией и единым форматом ошибок { error: { code, message } }
- sync/backup — заглушки 501 (billmanager-адаптеры переносятся отдельно)
- legacy Express оставлен как runtime по умолчанию (RUNTIME=express)

Infra:
- Dockerfile multi-stage под pnpm workspaces
- .dockerignore и docker-compose обновлены под monorepo

Rules:
- удалены нерелевантные правила (rust, cloudflare, server/frontend-conventions)
- project-structure.mdc и AGENTS.md переписаны под monorepo
- frontend-shadcn.mdc, shadcn-ui-production.mdc, sqlite.mdc обновлены

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-06-26 13:42:05 +07:00
co-authored by Cursor
parent 8408155be6
commit 6fbd1a9113
202 changed files with 16734 additions and 12145 deletions
+29
View File
@@ -0,0 +1,29 @@
/**
* BILLmanager: URL и тип API задаются на хостере (providers), учётные данные — на аккаунте.
* Поддержка fallback на поля аккаунта для старых данных до миграции.
*/
/**
* @param {object|null|undefined} accountRow
* @param {object|null|undefined} providerRow
* @returns {{ apiType: string, apiBaseUrl: string }}
*/
export function resolveBillmanagerApi(accountRow, providerRow) {
const apiType = String(providerRow?.apiType || accountRow?.apiType || '').trim()
const apiBaseUrl = String(providerRow?.apiBaseUrl || accountRow?.apiBaseUrl || '').trim()
return { apiType, apiBaseUrl }
}
/**
* Объект аккаунта с подставленным URL для syncFromBillmanager / balance.
* @param {object} accountRow
* @param {object|null|undefined} providerRow
* @returns {object|null} null если не готово к запросам API
*/
export function billmanagerAccountRowForSync(accountRow, providerRow) {
if (!accountRow) return null
const { apiType, apiBaseUrl } = resolveBillmanagerApi(accountRow, providerRow)
const cred = String(accountRow.apiCredentials || '').trim()
if (apiType !== 'billmanager' || !apiBaseUrl || !cred) return null
return { ...accountRow, apiType: 'billmanager', apiBaseUrl }
}
+51
View File
@@ -0,0 +1,51 @@
/**
* Map database rows to API response format
*/
/**
* @param {object} row - provider_account row
* @returns {object} sanitized (apiCredentials hidden)
*/
export function sanitizeAccount(row) {
if (!row) return row
const { apiCredentials, ...rest } = row
return { ...rest, apiCredentialsSet: Boolean(apiCredentials) }
}
/**
* @param {object} row - active_tariffs row
* @returns {object|null}
*/
export function rowToActiveTariff(row) {
if (!row) return null
return {
...row,
orderAvailable: Boolean(row.orderAvailable),
ramGb: row.ramGb != null ? Number(row.ramGb) : 0,
}
}
/**
* @param {object} row - tariff_sync_options row
* @returns {object|null}
*/
export function rowToTariffSyncOptions(row) {
if (!row) return null
let datacenters = []
let periods = []
try {
datacenters = row.datacenters ? JSON.parse(row.datacenters) : []
} catch {
/* ignore parse error */
}
try {
periods = row.periods ? JSON.parse(row.periods) : []
} catch {
/* ignore parse error */
}
return {
...row,
datacenters: Array.isArray(datacenters) ? datacenters : [],
periods: Array.isArray(periods) ? periods : [],
}
}