diff --git a/server/internal/tenant/app/article_service.go b/server/internal/tenant/app/article_service.go index 699256d..a230379 100644 --- a/server/internal/tenant/app/article_service.go +++ b/server/internal/tenant/app/article_service.go @@ -110,24 +110,18 @@ const articlePublishStatusAggregateJoin = ` BOOL_OR(latest.status_bucket = 'success') AS has_success, BOOL_OR(latest.status_bucket = 'failed') AS has_failed FROM ( - SELECT DISTINCT ON ( - COALESCE(pr.target_type, 'platform_account'), - COALESCE(pr.platform_account_id, pr.target_connection_id) - ) + SELECT DISTINCT ON (` + publishRecordTargetIdentityExprSQL + `) CASE WHEN pr.status IN ('success', 'published', 'publish_success') THEN 'success' WHEN pr.status IN ('publishing', 'pending', 'queued', 'running') THEN 'publishing' ELSE 'failed' END AS status_bucket FROM publish_records pr + LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id WHERE pr.tenant_id = a.tenant_id AND pr.article_id = a.id AND pr.deleted_at IS NULL - ORDER BY - COALESCE(pr.target_type, 'platform_account'), - COALESCE(pr.platform_account_id, pr.target_connection_id), - pr.created_at DESC, - pr.id DESC + ORDER BY ` + publishRecordTargetIdentityExprSQL + `, pr.created_at DESC, pr.id DESC ) latest ) publish_status_summary ON true` diff --git a/server/internal/tenant/app/publish_record_support.go b/server/internal/tenant/app/publish_record_support.go index 04b85aa..c3d04a2 100644 --- a/server/internal/tenant/app/publish_record_support.go +++ b/server/internal/tenant/app/publish_record_support.go @@ -45,6 +45,12 @@ type monitoringArticleAliasInput struct { ExternalManageURL *string } +const publishRecordTargetIdentityExprSQL = `CASE + WHEN COALESCE(pr.target_type, 'platform_account') = 'enterprise_site' + THEN 'enterprise_site:' || COALESCE(pr.target_connection_id::TEXT, '') + ELSE 'platform_account:' || pr.platform_id || ':' || COALESCE(NULLIF(pa.platform_uid, ''), pr.platform_account_id::TEXT, '') + END` + func loadPublishableArticleMeta(ctx context.Context, tx pgx.Tx, tenantID, brandID, articleID int64) (*publishableArticleMeta, error) { var generateStatus string var wizardStateJSON []byte @@ -664,9 +670,14 @@ func recalculatePublishBatchStatus(ctx context.Context, tx pgx.Tx, publishBatchI func recalculateArticlePublishStatus(ctx context.Context, tx pgx.Tx, tenantID, articleID int64) (string, error) { rows, err := tx.Query(ctx, ` SELECT status - FROM publish_records - WHERE tenant_id = $1 AND article_id = $2 - AND deleted_at IS NULL + FROM ( + SELECT DISTINCT ON (`+publishRecordTargetIdentityExprSQL+`) pr.status + FROM publish_records pr + LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id + WHERE pr.tenant_id = $1 AND pr.article_id = $2 + AND pr.deleted_at IS NULL + ORDER BY `+publishRecordTargetIdentityExprSQL+`, pr.created_at DESC, pr.id DESC + ) latest `, tenantID, articleID) if err != nil { return "", response.ErrInternal(50055, "article_publish_status_query_failed", "failed to inspect article publish status") diff --git a/server/internal/tenant/app/publish_record_support_test.go b/server/internal/tenant/app/publish_record_support_test.go index fb7a46a..96591bb 100644 --- a/server/internal/tenant/app/publish_record_support_test.go +++ b/server/internal/tenant/app/publish_record_support_test.go @@ -231,3 +231,40 @@ func TestRecalculateArticlePublishStatusDefaultsToUnpublished(t *testing.T) { t.Fatalf("recalculateArticlePublishStatus() = %q, want unpublished", got) } } + +func TestRecalculateArticlePublishStatusUsesLatestRecordPerPublishTarget(t *testing.T) { + tx := &capturePublishDedupLookupTx{rows: emptyPublishDedupRows{}} + + _, err := recalculateArticlePublishStatus(context.Background(), tx, 1, 2) + if err != nil { + t.Fatalf("recalculateArticlePublishStatus() error = %v", err) + } + + normalized := normalizeSQLWhitespace(tx.sql) + for _, want := range []string{ + "SELECT DISTINCT ON", + "LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id", + "pa.platform_uid", + "pr.platform_id", + "pr.created_at DESC, pr.id DESC", + } { + if !strings.Contains(normalized, want) { + t.Fatalf("recalculate article publish status SQL missing %q: %s", want, normalized) + } + } +} + +func TestArticlePublishStatusAggregateUsesLatestRecordPerPublishTarget(t *testing.T) { + normalized := normalizeSQLWhitespace(articlePublishStatusAggregateJoin) + for _, want := range []string{ + "SELECT DISTINCT ON", + "LEFT JOIN platform_accounts pa ON pa.id = pr.platform_account_id", + "pa.platform_uid", + "pr.platform_id", + "pr.created_at DESC, pr.id DESC", + } { + if !strings.Contains(normalized, want) { + t.Fatalf("article publish aggregate SQL missing %q: %s", want, normalized) + } + } +}