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 <cursoragent@cursor.com>
This commit is contained in:
1 parent
8408155be6
commit
6fbd1a9113
202 files changed
+16734
-12145
No files matched your search
@@ -0,0 +1,175 @@
|
||||
import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'
|
||||
import { sql } from 'drizzle-orm'
|
||||
|
||||
export const providers = sqliteTable('providers', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
website: text('website'),
|
||||
contact: text('contact'),
|
||||
baseCurrency: text('baseCurrency'),
|
||||
usdRate: text('usdRate'),
|
||||
eurRate: text('eurRate'),
|
||||
notes: text('notes'),
|
||||
apiType: text('apiType'),
|
||||
apiBaseUrl: text('apiBaseUrl'),
|
||||
})
|
||||
|
||||
export const providerAccounts = sqliteTable('provider_accounts', {
|
||||
id: text('id').primaryKey(),
|
||||
providerId: text('providerId')
|
||||
.notNull()
|
||||
.references(() => providers.id),
|
||||
name: text('name').notNull(),
|
||||
panelUrl: text('panelUrl'),
|
||||
currency: text('currency'),
|
||||
billingMode: text('billingMode'),
|
||||
notes: text('notes'),
|
||||
apiType: text('apiType'),
|
||||
apiBaseUrl: text('apiBaseUrl'),
|
||||
apiCredentials: text('apiCredentials'),
|
||||
balanceApi: real('balance_api'),
|
||||
balanceCurrency: text('balance_currency'),
|
||||
balanceUpdatedAt: text('balance_updated_at'),
|
||||
enoughmoneyto: text('enoughmoneyto'),
|
||||
balanceAlertBelow: real('balance_alert_below'),
|
||||
})
|
||||
|
||||
export const serverProjects = sqliteTable('server_projects', {
|
||||
id: text('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
color: text('color'),
|
||||
sortOrder: integer('sortOrder').default(0),
|
||||
notes: text('notes'),
|
||||
createdAt: text('createdAt'),
|
||||
})
|
||||
|
||||
export const vps = sqliteTable('vps', {
|
||||
id: text('id').primaryKey(),
|
||||
ip: text('ip'),
|
||||
ipv6: text('ipv6'),
|
||||
additionalIps: text('additionalIps'),
|
||||
dns: text('dns'),
|
||||
providerId: text('providerId').references(() => providers.id),
|
||||
providerAccountId: text('providerAccountId').references(() => providerAccounts.id),
|
||||
country: text('country'),
|
||||
city: text('city'),
|
||||
datacenter: text('datacenter'),
|
||||
os: text('os'),
|
||||
vcpu: integer('vcpu'),
|
||||
ramGb: integer('ramGb'),
|
||||
diskGb: integer('diskGb'),
|
||||
diskType: text('diskType'),
|
||||
virtualization: text('virtualization'),
|
||||
bandwidthTb: integer('bandwidthTb'),
|
||||
sshPort: integer('sshPort'),
|
||||
rootUser: text('rootUser'),
|
||||
purpose: text('purpose'),
|
||||
environment: text('environment'),
|
||||
project: text('project'),
|
||||
projectId: text('projectId').references(() => serverProjects.id),
|
||||
monitoringEnabled: integer('monitoringEnabled'),
|
||||
backupEnabled: integer('backupEnabled'),
|
||||
status: text('status'),
|
||||
tariffType: text('tariffType'),
|
||||
currency: text('currency'),
|
||||
dailyRate: real('dailyRate'),
|
||||
monthlyRate: real('monthlyRate'),
|
||||
createdAt: text('createdAt'),
|
||||
paidUntil: text('paidUntil'),
|
||||
notes: text('notes'),
|
||||
userOverrides: text('userOverrides'),
|
||||
})
|
||||
|
||||
export const payments = sqliteTable('payments', {
|
||||
id: text('id').primaryKey(),
|
||||
type: text('type').notNull(),
|
||||
date: text('date').notNull(),
|
||||
amount: real('amount').notNull(),
|
||||
currency: text('currency'),
|
||||
providerAccountId: text('providerAccountId').references(() => providerAccounts.id),
|
||||
vpsId: text('vpsId').references(() => vps.id),
|
||||
note: text('note'),
|
||||
})
|
||||
|
||||
export const balanceLedger = sqliteTable('balance_ledger', {
|
||||
id: text('id').primaryKey(),
|
||||
type: text('type').notNull(),
|
||||
date: text('date').notNull(),
|
||||
amount: real('amount').notNull(),
|
||||
currency: text('currency'),
|
||||
direction: text('direction'),
|
||||
providerAccountId: text('providerAccountId').references(() => providerAccounts.id),
|
||||
vpsId: text('vpsId').references(() => vps.id),
|
||||
note: text('note'),
|
||||
})
|
||||
|
||||
export const settings = sqliteTable('settings', {
|
||||
id: text('id').primaryKey(),
|
||||
baseCurrency: text('baseCurrency'),
|
||||
ratesUrl: text('ratesUrl'),
|
||||
autoConvert: integer('autoConvert'),
|
||||
ratesUpdatedAt: text('ratesUpdatedAt'),
|
||||
syncEnabled: integer('syncEnabled'),
|
||||
syncIntervalMinutes: integer('syncIntervalMinutes'),
|
||||
syncTariffsIntervalMinutes: integer('syncTariffsIntervalMinutes'),
|
||||
customFields: text('customFields'),
|
||||
telegramBotToken: text('telegramBotToken'),
|
||||
telegramChatId: text('telegramChatId'),
|
||||
notifyPaymentExpiryEnabled: integer('notifyPaymentExpiryEnabled'),
|
||||
notifyNewTariffsEnabled: integer('notifyNewTariffsEnabled'),
|
||||
telegramMessageThreadId: text('telegramMessageThreadId'),
|
||||
notifyLowBalanceEnabled: integer('notifyLowBalanceEnabled'),
|
||||
notifySyncDigestEnabled: integer('notifySyncDigestEnabled'),
|
||||
})
|
||||
|
||||
export const syncLog = sqliteTable('sync_log', {
|
||||
id: text('id').primaryKey(),
|
||||
accountId: text('accountId')
|
||||
.notNull()
|
||||
.references(() => providerAccounts.id),
|
||||
startedAt: text('startedAt').notNull(),
|
||||
finishedAt: text('finishedAt'),
|
||||
status: text('status'),
|
||||
vpsCount: integer('vpsCount'),
|
||||
paymentsCount: integer('paymentsCount'),
|
||||
error: text('error'),
|
||||
summary: text('summary'),
|
||||
})
|
||||
|
||||
export const activeTariffs = sqliteTable('active_tariffs', {
|
||||
id: text('id').primaryKey(),
|
||||
providerAccountId: text('providerAccountId')
|
||||
.notNull()
|
||||
.references(() => providerAccounts.id),
|
||||
providerId: text('providerId')
|
||||
.notNull()
|
||||
.references(() => providers.id),
|
||||
externalId: text('externalId').notNull(),
|
||||
datacenterKey: text('datacenterKey'),
|
||||
datacenterName: text('datacenterName'),
|
||||
name: text('name'),
|
||||
desc: text('desc'),
|
||||
vcpu: integer('vcpu'),
|
||||
ramGb: real('ramGb'),
|
||||
diskGb: integer('diskGb'),
|
||||
diskType: text('diskType'),
|
||||
virtualization: text('virtualization'),
|
||||
channel: text('channel'),
|
||||
location: text('location'),
|
||||
country: text('country'),
|
||||
cpuModel: text('cpuModel'),
|
||||
orderAvailable: integer('orderAvailable'),
|
||||
price: text('price'),
|
||||
syncedAt: text('syncedAt'),
|
||||
})
|
||||
|
||||
export const tariffSyncOptions = sqliteTable('tariff_sync_options', {
|
||||
providerAccountId: text('providerAccountId')
|
||||
.primaryKey()
|
||||
.references(() => providerAccounts.id),
|
||||
datacenters: text('datacenters'),
|
||||
periods: text('periods'),
|
||||
syncedAt: text('syncedAt'),
|
||||
})
|
||||
|
||||
export const now = sql`(datetime('now'))`
|
||||
Reference in new issue
Block a user