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:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user