feat(db): перевести хранилище с SQLite на PostgreSQL
Docker images / prepare-release (push) Successful in 12s
Docker images / backend-test (push) Successful in 4m9s
Docker images / frontend-image (push) Successful in 4m28s
Docker images / updater-image (push) Successful in 58s
Docker images / backend-image (push) Successful in 2m49s
Docker images / notify-webhook (push) Skipped
Docker images / publish-release (push) Successful in 11s

При старте backend накатывает схему PostgreSQL 18 и, если база пустая, один раз импортирует mikrotik.db с тома. Повторный старт не копирует данные. Бэкап в UI идёт через pg_dump.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
DenozordecandCursor committed 2026-09-08 01:36:48 +07:00
1 parent 0e5fb065e2
commit ec43591a99
128 files changed
+4304 -3639

No files matched your search

+15 -19
View File
@@ -17,7 +17,9 @@ export type AlertOutboxPayload = {
}
}
function safeParsePayload(raw: string): AlertOutboxPayload | null {
function safeParsePayload(raw: unknown): AlertOutboxPayload | null {
if (raw && typeof raw === "object") return raw as AlertOutboxPayload
if (typeof raw !== "string") return null
try {
return JSON.parse(raw) as AlertOutboxPayload
} catch {
@@ -25,21 +27,20 @@ function safeParsePayload(raw: string): AlertOutboxPayload | null {
}
}
export function enqueueTelegramOutbox(input: {
export async function enqueueTelegramOutbox(input: {
id: string
dedupeKey: string
payload: AlertOutboxPayload
maxRetries?: number
}): boolean {
const existing = db
}): Promise<boolean> {
const existing = (await db
.select()
.from(alertOutbox)
.where(eq(alertOutbox.dedupeKey, input.dedupeKey))
.limit(1)
.all()[0]
.limit(1))[0]
if (existing && (existing.status === "pending" || existing.status === "sent")) return false
const nowIso = new Date().toISOString()
db.insert(alertOutbox)
await db.insert(alertOutbox)
.values({
id: input.id,
dedupeKey: input.dedupeKey,
@@ -48,22 +49,20 @@ export function enqueueTelegramOutbox(input: {
retryCount: 0,
maxRetries: Math.max(1, Math.floor(input.maxRetries ?? 3)),
nextAttemptAt: nowIso,
payloadJson: JSON.stringify(input.payload),
payloadJson: input.payload,
})
.run()
return true
}
export function dispatchPendingOutbox(limit = 25): Promise<{ sent: number; failed: number; errors: string[] }> {
return (async () => {
const nowIso = new Date().toISOString()
const rows = db
const rows = await db
.select()
.from(alertOutbox)
.where(and(eq(alertOutbox.status, "pending"), lte(alertOutbox.nextAttemptAt, nowIso)))
.orderBy(asc(alertOutbox.createdAt))
.limit(Math.max(1, limit))
.all()
let sent = 0
let failed = 0
@@ -72,27 +71,25 @@ export function dispatchPendingOutbox(limit = 25): Promise<{ sent: number; faile
for (const row of rows) {
const payload = safeParsePayload(row.payloadJson)
if (!payload) {
db.update(alertOutbox)
await db.update(alertOutbox)
.set({
status: "failed",
lastError: "invalid payload_json",
})
.where(eq(alertOutbox.id, row.id))
.run()
failed += 1
continue
}
const send = await sendTelegramAlertMessage({ text: payload.text, chatId: payload.chatId })
if (send.ok) {
db.update(alertOutbox)
await db.update(alertOutbox)
.set({
status: "sent",
sentAt: new Date().toISOString(),
lastError: null,
})
.where(eq(alertOutbox.id, row.id))
.run()
appendAlertHistory({
await appendAlertHistory({
id: payload.history.id,
ruleId: payload.history.ruleId ?? null,
groupId: payload.history.groupId ?? null,
@@ -107,7 +104,7 @@ export function dispatchPendingOutbox(limit = 25): Promise<{ sent: number; faile
}
const nextRetry = row.retryCount + 1
const exhausted = nextRetry >= row.maxRetries
db.update(alertOutbox)
await db.update(alertOutbox)
.set({
retryCount: nextRetry,
status: exhausted ? "failed" : "pending",
@@ -115,9 +112,8 @@ export function dispatchPendingOutbox(limit = 25): Promise<{ sent: number; faile
lastError: send.error,
})
.where(eq(alertOutbox.id, row.id))
.run()
if (exhausted) {
appendAlertHistory({
await appendAlertHistory({
id: payload.history.id,
ruleId: payload.history.ruleId ?? null,
groupId: payload.history.groupId ?? null,