fix: enhance publish record deletion logic and add soft delete functionality
Backend CI / Backend (push) Successful in 14m47s

This commit is contained in:
2026-06-19 17:20:35 +08:00
parent 8a1623b9c6
commit 18aa681b01
10 changed files with 49 additions and 39 deletions
+18 -6
View File
@@ -425,16 +425,17 @@ func (s *MediaService) DeletePublishRecord(ctx context.Context, recordID int64)
var publishBatchID int64 var publishBatchID int64
var articleID int64 var articleID int64
var cancelErrorJSON []byte var publishStatus string
cancelErrorJSON, err = marshalOptionalJSON(map[string]any{ cancelErrorJSONBytes, err := marshalOptionalJSON(map[string]any{
"reason": "publish_record_deleted", "reason": "publish_record_deleted",
"source": "tenant_web", "source": "tenant_web",
}) })
if err != nil { if err != nil {
return nil, response.ErrInternal(50044, "publish_record_delete_payload_failed", "failed to delete publish record") return nil, response.ErrInternal(50044, "publish_record_delete_payload_failed", "failed to delete publish record")
} }
cancelErrorJSON := string(cancelErrorJSONBytes)
err = tx.QueryRow(ctx, ` err = tx.QueryRow(ctx, `
SELECT pr.publish_batch_id, pr.article_id SELECT pr.publish_batch_id, pr.article_id, pr.status
FROM publish_records pr FROM publish_records pr
JOIN articles a JOIN articles a
ON a.id = pr.article_id ON a.id = pr.article_id
@@ -447,7 +448,7 @@ func (s *MediaService) DeletePublishRecord(ctx context.Context, recordID int64)
AND pb.initiator_user_id = $3 AND pb.initiator_user_id = $3
AND pr.deleted_at IS NULL AND pr.deleted_at IS NULL
FOR UPDATE OF pr FOR UPDATE OF pr
`, recordID, actor.TenantID, actor.UserID).Scan(&publishBatchID, &articleID) `, recordID, actor.TenantID, actor.UserID).Scan(&publishBatchID, &articleID, &publishStatus)
if err != nil { if err != nil {
if errors.Is(err, pgx.ErrNoRows) { if errors.Is(err, pgx.ErrNoRows) {
return nil, response.ErrNotFound(40435, "publish_record_not_found", "publish record not found") return nil, response.ErrNotFound(40435, "publish_record_not_found", "publish record not found")
@@ -468,10 +469,11 @@ func (s *MediaService) DeletePublishRecord(ctx context.Context, recordID int64)
return nil, response.ErrNotFound(40435, "publish_record_not_found", "publish record not found") return nil, response.ErrNotFound(40435, "publish_record_not_found", "publish record not found")
} }
if publishRecordMayHaveQueuedTask(publishStatus) {
if _, err := tx.Exec(ctx, ` if _, err := tx.Exec(ctx, `
UPDATE desktop_tasks UPDATE desktop_tasks
SET status = 'aborted', SET status = 'aborted',
error = $3, error = $3::jsonb,
active_attempt_id = NULL, active_attempt_id = NULL,
lease_token_hash = NULL, lease_token_hash = NULL,
lease_expires_at = NULL, lease_expires_at = NULL,
@@ -479,10 +481,11 @@ func (s *MediaService) DeletePublishRecord(ctx context.Context, recordID int64)
WHERE tenant_id = $1 WHERE tenant_id = $1
AND kind = 'publish' AND kind = 'publish'
AND status = 'queued' AND status = 'queued'
AND payload->>'publish_record_id' = $2 AND payload->>'publish_record_id' = $2::text
`, actor.TenantID, recordID, cancelErrorJSON); err != nil { `, actor.TenantID, recordID, cancelErrorJSON); err != nil {
return nil, response.ErrInternal(50044, "publish_record_delete_cancel_task_failed", "failed to cancel queued publish task before deleting record") return nil, response.ErrInternal(50044, "publish_record_delete_cancel_task_failed", "failed to cancel queued publish task before deleting record")
} }
}
batchStatus, err := recalculatePublishBatchStatus(ctx, tx, publishBatchID) batchStatus, err := recalculatePublishBatchStatus(ctx, tx, publishBatchID)
if err != nil { if err != nil {
@@ -532,6 +535,15 @@ func publishPlatformRequiresCover(platformID string) bool {
} }
} }
func publishRecordMayHaveQueuedTask(status string) bool {
switch strings.TrimSpace(status) {
case "queued", "publishing", "pending", "running":
return true
default:
return false
}
}
func pointerStringValue(value *string) string { func pointerStringValue(value *string) string {
if value == nil { if value == nil {
return "" return ""
@@ -115,7 +115,7 @@ func cancelQueuedPublishTasksForUnavailableArticle(ctx context.Context, tx pgx.T
if tx == nil || tenantID <= 0 || articleID <= 0 { if tx == nil || tenantID <= 0 || articleID <= 0 {
return nil return nil
} }
errorJSON, err := marshalOptionalJSON(map[string]any{ errorJSONBytes, err := marshalOptionalJSON(map[string]any{
"code": "article_unavailable", "code": "article_unavailable",
"message": "article is unavailable before publishing", "message": "article is unavailable before publishing",
"source": "article_unavailable_cleanup", "source": "article_unavailable_cleanup",
@@ -123,6 +123,7 @@ func cancelQueuedPublishTasksForUnavailableArticle(ctx context.Context, tx pgx.T
if err != nil { if err != nil {
return response.ErrInternal(50120, "publish_task_cancel_payload_failed", "failed to encode publish task cancellation payload") return response.ErrInternal(50120, "publish_task_cancel_payload_failed", "failed to encode publish task cancellation payload")
} }
errorJSON := string(errorJSONBytes)
if _, err := tx.Exec(ctx, ` if _, err := tx.Exec(ctx, `
WITH affected_records AS ( WITH affected_records AS (
@@ -154,7 +155,7 @@ func cancelQueuedPublishTasksForUnavailableArticle(ctx context.Context, tx pgx.T
updated_tasks AS ( updated_tasks AS (
UPDATE desktop_tasks dt UPDATE desktop_tasks dt
SET status = 'aborted', SET status = 'aborted',
error = $3, error = $3::jsonb,
active_attempt_id = NULL, active_attempt_id = NULL,
lease_token_hash = NULL, lease_token_hash = NULL,
lease_expires_at = NULL, lease_expires_at = NULL,
@@ -280,7 +281,7 @@ func cancelQueuedPublishTasksForDesktopAccountUnbind(
if tx == nil || tenantID <= 0 || workspaceID <= 0 || accountID == uuid.Nil { if tx == nil || tenantID <= 0 || workspaceID <= 0 || accountID == uuid.Nil {
return 0, nil return 0, nil
} }
errorJSON, err := marshalOptionalJSON(map[string]any{ errorJSONBytes, err := marshalOptionalJSON(map[string]any{
"code": "desktop_account_unbound", "code": "desktop_account_unbound",
"message": "desktop account was unbound before publishing", "message": "desktop account was unbound before publishing",
"source": "desktop_account_unbind", "source": "desktop_account_unbind",
@@ -288,6 +289,7 @@ func cancelQueuedPublishTasksForDesktopAccountUnbind(
if err != nil { if err != nil {
return 0, response.ErrInternal(50120, "publish_task_cancel_payload_failed", "failed to encode publish task cancellation payload") return 0, response.ErrInternal(50120, "publish_task_cancel_payload_failed", "failed to encode publish task cancellation payload")
} }
errorJSON := string(errorJSONBytes)
rows, err := tx.Query(ctx, ` rows, err := tx.Query(ctx, `
SELECT DISTINCT pr.id, pr.publish_batch_id, pr.article_id SELECT DISTINCT pr.id, pr.publish_batch_id, pr.article_id
@@ -354,7 +356,7 @@ func cancelQueuedPublishTasksForDesktopAccountUnbind(
tag, err := tx.Exec(ctx, ` tag, err := tx.Exec(ctx, `
UPDATE desktop_tasks dt UPDATE desktop_tasks dt
SET status = 'aborted', SET status = 'aborted',
error = $4, error = $4::jsonb,
active_attempt_id = NULL, active_attempt_id = NULL,
lease_token_hash = NULL, lease_token_hash = NULL,
lease_expires_at = NULL, lease_expires_at = NULL,
@@ -1,9 +1,2 @@
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_visible_status;
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_article_account_active;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_article_account_active
ON publish_records (tenant_id, article_id, platform_account_id, updated_at DESC, id DESC)
WHERE status IN ('queued', 'publishing', 'success');
ALTER TABLE publish_records ALTER TABLE publish_records
DROP COLUMN IF EXISTS deleted_at; DROP COLUMN IF EXISTS deleted_at;
@@ -1,12 +1,2 @@
ALTER TABLE publish_records ALTER TABLE publish_records
ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ; ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMPTZ;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_visible_status
ON publish_records (tenant_id, status, created_at DESC, id DESC)
WHERE deleted_at IS NULL;
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_article_account_active;
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_article_account_active
ON publish_records (tenant_id, article_id, platform_account_id, updated_at DESC, id DESC)
WHERE status IN ('queued', 'publishing', 'success')
AND deleted_at IS NULL;
@@ -0,0 +1 @@
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_visible_status;
@@ -0,0 +1,3 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_visible_status
ON publish_records (tenant_id, status, created_at DESC, id DESC)
WHERE deleted_at IS NULL;
@@ -0,0 +1,3 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_article_account_active
ON publish_records (tenant_id, article_id, platform_account_id, updated_at DESC, id DESC)
WHERE status IN ('queued', 'publishing', 'success');
@@ -0,0 +1 @@
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_article_account_active;
@@ -0,0 +1 @@
DROP INDEX CONCURRENTLY IF EXISTS idx_publish_records_article_account_active;
@@ -0,0 +1,4 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_publish_records_article_account_active
ON publish_records (tenant_id, article_id, platform_account_id, updated_at DESC, id DESC)
WHERE status IN ('queued', 'publishing', 'success')
AND deleted_at IS NULL;