fix(alerts): защитить ручной повтор очереди проверкой версии

This commit is contained in:
Denozordec committed 2026-10-03 19:14:27 +07:00
1 parent 89c31289bd
commit 7d0608bbce
16 files changed
+86 -17

No files matched your search

+9 -1
View File
@@ -1,3 +1,4 @@
import {executeOutboxRetry} from "../alerts-outbox-retry.js"
import {readOutboxOverview} from "../alerts-outbox-overview.js"
import {and,asc,desc,eq,inArray,lte,sql} from "drizzle-orm"
import {db} from "../../db/index.js"
@@ -44,6 +45,13 @@ export async function getAlertsOutboxOverview(){
return readOutboxOverview<Parameters<Parameters<typeof db.transaction>[0]>[0]>({
transaction:(work,options)=>db.transaction(work,options),
counts:async context=>{const result=await context.execute(sql`SELECT status, COUNT(*)::bigint AS count FROM alert_outbox GROUP BY status`);return result.rows},
items:async context=>{const rows=await context.select().from(alertOutbox).where(inArray(alertOutbox.status,["processing","unknown"])).orderBy(desc(alertOutbox.nextAttemptAt),desc(alertOutbox.id)).limit(20);return rows.map(row=>{const payload=row.payloadJson;const history=typeof payload==="object"&&payload!==null&&"history" in payload?payload.history:null;const ruleName=typeof history==="object"&&history!==null&&"ruleName" in history&&typeof history.ruleName==="string"?history.ruleName:null;return {id:row.id,status:row.status,ruleName,createdAt:row.createdAt,attemptedAt:row.nextAttemptAt}})},
items:async context=>{const rows=await context.select().from(alertOutbox).where(inArray(alertOutbox.status,["processing","unknown"])).orderBy(desc(alertOutbox.nextAttemptAt),desc(alertOutbox.id)).limit(20);return rows.map(row=>{const payload=row.payloadJson;const history=typeof payload==="object"&&payload!==null&&"history" in payload?payload.history:null;const ruleName=typeof history==="object"&&history!==null&&"ruleName" in history&&typeof history.ruleName==="string"?history.ruleName:null;return {id:row.id,resolutionVersion:row.resolutionVersion,status:row.status,ruleName,createdAt:row.createdAt,attemptedAt:row.nextAttemptAt}})},
})
}
export async function retryUnknownOutbox(id:string,input:unknown){
return executeOutboxRetry<Parameters<Parameters<typeof db.transaction>[0]>[0]>(id,input,{
transaction:work=>db.transaction(work),
update:async(tx,messageId,expectedVersion)=>{const [receipt]=await tx.update(alertOutbox).set({status:"pending",resolutionVersion:sql`resolution_version + 1`,nextAttemptAt:new Date().toISOString(),lastError:null}).where(and(eq(alertOutbox.id,messageId),eq(alertOutbox.status,"unknown"),eq(alertOutbox.channel,"telegram"),eq(alertOutbox.resolutionVersion,expectedVersion))).returning({id:alertOutbox.id,status:alertOutbox.status,resolutionVersion:alertOutbox.resolutionVersion});return receipt},
})
}