feat: implement brand asset cleanup event handling and reconciliation logic
Backend CI / Backend (push) Failing after 6m54s

This commit is contained in:
2026-06-21 08:26:56 +08:00
parent 05a69c7c89
commit c1b06dab18
11 changed files with 744 additions and 15 deletions
@@ -0,0 +1,50 @@
package app
import (
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
const BrandAssetCleanupNotifyChannel = "brand_asset_cleanup_events"
type brandAssetCleanupEventQuerier interface {
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
QueryRow(context.Context, string, ...any) pgx.Row
}
func EnqueueBrandAssetCleanupEvent(ctx context.Context, q brandAssetCleanupEventQuerier, tenantID, brandID int64) (int64, error) {
var eventID int64
err := q.QueryRow(ctx, `
INSERT INTO brand_asset_cleanup_events (tenant_id, brand_id, status, next_attempt_at, last_error, processed_at)
VALUES ($1, $2, 'pending', NOW(), NULL, NULL)
ON CONFLICT (tenant_id, brand_id) WHERE status IN ('pending', 'processing', 'failed') DO UPDATE
SET status = CASE
WHEN brand_asset_cleanup_events.status = 'processing' THEN brand_asset_cleanup_events.status
ELSE 'pending'
END,
next_attempt_at = CASE
WHEN brand_asset_cleanup_events.status = 'processing' THEN brand_asset_cleanup_events.next_attempt_at
ELSE NOW()
END,
last_error = CASE
WHEN brand_asset_cleanup_events.status = 'processing' THEN brand_asset_cleanup_events.last_error
ELSE NULL
END,
processed_at = NULL,
updated_at = NOW()
RETURNING id
`, tenantID, brandID).Scan(&eventID)
if err != nil {
return 0, err
}
return eventID, nil
}
func NotifyBrandAssetCleanup(ctx context.Context, q brandAssetCleanupEventQuerier) error {
_, err := q.Exec(ctx, `
SELECT pg_notify($1, '')
`, BrandAssetCleanupNotifyChannel)
return err
}
@@ -309,6 +309,13 @@ func (s *BrandService) Delete(ctx context.Context, id int64) error {
_, _ = tx.Exec(ctx, `UPDATE brand_questions SET deleted_at = NOW(), updated_at = NOW() WHERE brand_id = $1 AND tenant_id = $2 AND deleted_at IS NULL`, id, actor.TenantID)
_, _ = tx.Exec(ctx, `UPDATE competitors SET deleted_at = NOW(), updated_at = NOW() WHERE brand_id = $1 AND tenant_id = $2 AND deleted_at IS NULL`, id, actor.TenantID)
if _, err := EnqueueBrandAssetCleanupEvent(ctx, tx, actor.TenantID, id); err != nil {
return response.ErrInternal(50010, "delete_failed", "failed to enqueue brand cleanup event")
}
if err := NotifyBrandAssetCleanup(ctx, tx); err != nil {
return response.ErrInternal(50010, "delete_failed", "failed to notify brand cleanup")
}
afterJSON, _ := json.Marshal(map[string]interface{}{"id": id, "status": "deleting", "cleanup": "background"})
if err := tx.Commit(ctx); err != nil {
return err