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
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@cfdm/shared",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./contracts/*": "./src/contracts/*.ts",
"./types/*": "./src/types/*.ts"
},
"dependencies": {
"zod": "^3.25.0"
}
}
@@ -0,0 +1,15 @@
import { z } from 'zod'
export const balanceLedgerSchema = z.object({
id: z.string().optional(),
type: z.string().min(1, 'Type is required'),
date: z.string().min(1, 'Date is required'),
amount: z.coerce.number(),
currency: z.string().optional().default(''),
direction: z.string().optional().default(''),
providerAccountId: z.string().optional().default(''),
vpsId: z.string().optional().default(''),
note: z.string().optional().default(''),
})
export type BalanceLedger = z.infer<typeof balanceLedgerSchema>
+14
View File
@@ -0,0 +1,14 @@
import { z } from 'zod'
export const paymentSchema = z.object({
id: z.string().optional(),
type: z.string().min(1, 'Type is required'),
date: z.string().min(1, 'Date is required'),
amount: z.coerce.number(),
currency: z.string().optional().default(''),
providerAccountId: z.string().optional().default(''),
vpsId: z.string().optional().default(''),
note: z.string().optional().default(''),
})
export type Payment = z.infer<typeof paymentSchema>
@@ -0,0 +1,15 @@
import { z } from 'zod'
export const providerAccountSchema = z.object({
id: z.string().optional(),
providerId: z.string().min(1, 'Provider is required'),
name: z.string().min(1, 'Name is required'),
panelUrl: z.string().optional().default(''),
currency: z.string().optional().default(''),
billingMode: z.string().optional().default(''),
notes: z.string().optional().default(''),
apiCredentials: z.string().optional().default(''),
balanceAlertBelow: z.union([z.number(), z.null()]).optional(),
})
export type ProviderAccount = z.infer<typeof providerAccountSchema>
+16
View File
@@ -0,0 +1,16 @@
import { z } from 'zod'
export const providerSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'Name is required'),
website: z.string().optional().default(''),
contact: z.string().optional().default(''),
baseCurrency: z.string().optional().default(''),
usdRate: z.string().optional().default(''),
eurRate: z.string().optional().default(''),
notes: z.string().optional().default(''),
apiType: z.string().optional().default(''),
apiBaseUrl: z.string().optional().default(''),
})
export type Provider = z.infer<typeof providerSchema>
+22
View File
@@ -0,0 +1,22 @@
import { z } from 'zod'
export const settingsSchema = z.object({
id: z.string().optional(),
baseCurrency: z.string().optional(),
ratesUrl: z.string().optional(),
autoConvert: z.boolean().optional(),
ratesUpdatedAt: z.string().optional(),
syncEnabled: z.boolean().optional(),
syncIntervalMinutes: z.coerce.number().optional(),
syncTariffsIntervalMinutes: z.coerce.number().optional(),
telegramBotToken: z.string().optional(),
telegramChatId: z.string().optional(),
telegramMessageThreadId: z.string().optional(),
notifyPaymentExpiryEnabled: z.boolean().optional(),
notifyNewTariffsEnabled: z.boolean().optional(),
notifyLowBalanceEnabled: z.boolean().optional(),
notifySyncDigestEnabled: z.boolean().optional(),
customFields: z.any().optional(),
})
export type Settings = z.infer<typeof settingsSchema>
+40
View File
@@ -0,0 +1,40 @@
import { z } from 'zod'
export const vpsSchema = z.object({
id: z.string().optional(),
ip: z.string().optional().default(''),
ipv6: z.string().optional().default(''),
additionalIps: z.array(z.string()).optional().default([]),
dns: z.string().optional().default(''),
providerId: z.string().optional().default(''),
providerAccountId: z.string().optional().default(''),
country: z.string().optional().default(''),
city: z.string().optional().default(''),
datacenter: z.string().optional().default(''),
os: z.string().optional().default(''),
vcpu: z.coerce.number().optional().default(0),
ramGb: z.coerce.number().optional().default(0),
diskGb: z.coerce.number().optional().default(0),
diskType: z.string().optional().default(''),
virtualization: z.string().optional().default(''),
bandwidthTb: z.coerce.number().optional().default(0),
sshPort: z.coerce.number().optional().default(22),
rootUser: z.string().optional().default(''),
purpose: z.string().optional().default(''),
environment: z.string().optional().default(''),
project: z.string().optional().default(''),
projectId: z.string().optional().default(''),
monitoringEnabled: z.coerce.boolean().optional().default(false),
backupEnabled: z.coerce.boolean().optional().default(false),
status: z.string().optional().default('active'),
tariffType: z.string().optional().default(''),
currency: z.string().optional().default(''),
dailyRate: z.union([z.coerce.number(), z.literal('')]).optional(),
monthlyRate: z.union([z.coerce.number(), z.literal('')]).optional(),
createdAt: z.string().optional(),
paidUntil: z.string().optional().default(''),
notes: z.string().optional().default(''),
userOverrides: z.union([z.array(z.string()), z.literal('clear')]).optional(),
})
export type VpsFormValues = z.infer<typeof vpsSchema>
+6
View File
@@ -0,0 +1,6 @@
export * from './contracts/provider'
export * from './contracts/provider-account'
export * from './contracts/vps'
export * from './contracts/payment'
export * from './contracts/balance-ledger'
export * from './contracts/settings'