fix(publish): scope desktop publish task list and retry by creator user
Now that desktop clients can be re-registered with a new id under the same user, listing/searching publish tasks by client.id missed the user's older history and the retry path forbade jobs created from a previous client. Switch the list/count queries to join desktop_publish_jobs and filter by created_by_user_id, and enforce the same ownership when retrying via desktop task. Qualify column names with the t. alias so the join doesn't break the existing ORDER BY clauses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -480,7 +480,7 @@ func (s *DesktopTaskService) ListPublishTasks(ctx context.Context, client *repos
|
|||||||
title,
|
title,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
`CASE WHEN status = 'in_progress' THEN 0 ELSE 1 END ASC, created_at ASC, desktop_id DESC`,
|
`CASE WHEN t.status = 'in_progress' THEN 0 ELSE 1 END ASC, t.created_at ASC, t.desktop_id DESC`,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -517,7 +517,7 @@ func (s *DesktopTaskService) ListPublishTasks(ctx context.Context, client *repos
|
|||||||
title,
|
title,
|
||||||
remaining,
|
remaining,
|
||||||
historyOffset,
|
historyOffset,
|
||||||
`updated_at DESC, desktop_id DESC`,
|
`t.updated_at DESC, t.desktop_id DESC`,
|
||||||
)
|
)
|
||||||
if listErr != nil {
|
if listErr != nil {
|
||||||
return nil, listErr
|
return nil, listErr
|
||||||
@@ -546,32 +546,38 @@ func (s *DesktopTaskService) listPublishTasksByStatuses(
|
|||||||
) ([]DesktopTaskView, error) {
|
) ([]DesktopTaskView, error) {
|
||||||
query := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
desktop_id,
|
t.desktop_id,
|
||||||
job_id,
|
t.job_id,
|
||||||
tenant_id,
|
t.tenant_id,
|
||||||
workspace_id,
|
t.workspace_id,
|
||||||
target_account_id,
|
t.target_account_id,
|
||||||
target_client_id,
|
t.target_client_id,
|
||||||
platform_id,
|
t.platform_id,
|
||||||
kind,
|
t.kind,
|
||||||
payload,
|
t.payload,
|
||||||
status,
|
t.status,
|
||||||
dedup_key,
|
t.dedup_key,
|
||||||
active_attempt_id,
|
t.active_attempt_id,
|
||||||
lease_expires_at,
|
t.lease_expires_at,
|
||||||
attempts,
|
t.attempts,
|
||||||
result,
|
t.result,
|
||||||
error,
|
t.error,
|
||||||
created_at,
|
t.created_at,
|
||||||
updated_at
|
t.updated_at
|
||||||
FROM desktop_tasks
|
FROM desktop_tasks AS t
|
||||||
WHERE workspace_id = $1
|
JOIN desktop_publish_jobs AS j
|
||||||
AND target_client_id = $2
|
ON j.desktop_id = t.job_id
|
||||||
AND kind = 'publish'
|
AND j.workspace_id = t.workspace_id
|
||||||
AND status = ANY($3)
|
WHERE t.workspace_id = $1
|
||||||
AND ($4 = '' OR COALESCE(payload->>'title', '') ILIKE '%' || $4 || '%')
|
AND j.created_by_user_id = $2
|
||||||
|
AND t.kind = 'publish'
|
||||||
|
AND t.status = ANY($3)
|
||||||
|
AND (
|
||||||
|
$4 = ''
|
||||||
|
OR COALESCE(t.payload->>'title', j.title, '') ILIKE '%' || $4 || '%'
|
||||||
|
)
|
||||||
`
|
`
|
||||||
args := []any{client.WorkspaceID, client.ID, statuses, title}
|
args := []any{client.WorkspaceID, client.UserID, statuses, title}
|
||||||
|
|
||||||
if strings.TrimSpace(orderBy) != "" {
|
if strings.TrimSpace(orderBy) != "" {
|
||||||
query += "\nORDER BY " + orderBy
|
query += "\nORDER BY " + orderBy
|
||||||
@@ -599,13 +605,19 @@ func (s *DesktopTaskService) countPublishTasksByStatuses(
|
|||||||
var count int
|
var count int
|
||||||
err := s.pool.QueryRow(ctx, `
|
err := s.pool.QueryRow(ctx, `
|
||||||
SELECT COUNT(1)
|
SELECT COUNT(1)
|
||||||
FROM desktop_tasks
|
FROM desktop_tasks AS t
|
||||||
WHERE workspace_id = $1
|
JOIN desktop_publish_jobs AS j
|
||||||
AND target_client_id = $2
|
ON j.desktop_id = t.job_id
|
||||||
AND kind = 'publish'
|
AND j.workspace_id = t.workspace_id
|
||||||
AND status = ANY($3)
|
WHERE t.workspace_id = $1
|
||||||
AND ($4 = '' OR COALESCE(payload->>'title', '') ILIKE '%' || $4 || '%')
|
AND j.created_by_user_id = $2
|
||||||
`, client.WorkspaceID, client.ID, statuses, title).Scan(&count)
|
AND t.kind = 'publish'
|
||||||
|
AND t.status = ANY($3)
|
||||||
|
AND (
|
||||||
|
$4 = ''
|
||||||
|
OR COALESCE(t.payload->>'title', j.title, '') ILIKE '%' || $4 || '%'
|
||||||
|
)
|
||||||
|
`, client.WorkspaceID, client.UserID, statuses, title).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, response.ErrInternal(50109, "desktop_publish_tasks_query_failed", "failed to list desktop publish tasks")
|
return 0, response.ErrInternal(50109, "desktop_publish_tasks_query_failed", "failed to list desktop publish tasks")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -281,8 +281,8 @@ func (s *PublishJobService) RetryByDesktopTask(
|
|||||||
if task.Kind != "publish" {
|
if task.Kind != "publish" {
|
||||||
return nil, response.ErrBadRequest(40095, "desktop_task_not_publish", "only publish tasks can be retried")
|
return nil, response.ErrBadRequest(40095, "desktop_task_not_publish", "only publish tasks can be retried")
|
||||||
}
|
}
|
||||||
if task.TargetClientID != client.ID {
|
if err := s.authorizePublishTaskForClientUser(ctx, client, task); err != nil {
|
||||||
return nil, response.ErrForbidden(40381, "desktop_task_not_target_client", "desktop task is assigned to another desktop client")
|
return nil, err
|
||||||
}
|
}
|
||||||
if task.Status == "queued" || task.Status == "in_progress" {
|
if task.Status == "queued" || task.Status == "in_progress" {
|
||||||
return nil, response.ErrConflict(40994, "desktop_task_retry_conflict", "desktop task is still pending or running")
|
return nil, response.ErrConflict(40994, "desktop_task_retry_conflict", "desktop task is still pending or running")
|
||||||
@@ -314,6 +314,34 @@ func (s *PublishJobService) RetryByDesktopTask(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublishJobService) authorizePublishTaskForClientUser(
|
||||||
|
ctx context.Context,
|
||||||
|
client *repository.DesktopClient,
|
||||||
|
task *repository.DesktopTask,
|
||||||
|
) error {
|
||||||
|
if client == nil || task == nil {
|
||||||
|
return response.ErrUnauthorized(40108, "desktop_client_missing", "desktop client context is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
var ownsTask bool
|
||||||
|
err := s.pool.QueryRow(ctx, `
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM desktop_publish_jobs
|
||||||
|
WHERE desktop_id = $1
|
||||||
|
AND workspace_id = $2
|
||||||
|
AND created_by_user_id = $3
|
||||||
|
)
|
||||||
|
`, task.JobID, client.WorkspaceID, client.UserID).Scan(&ownsTask)
|
||||||
|
if err != nil {
|
||||||
|
return response.ErrInternal(50115, "desktop_publish_job_lookup_failed", "failed to validate desktop publish task ownership")
|
||||||
|
}
|
||||||
|
if !ownsTask {
|
||||||
|
return response.ErrForbidden(40384, "desktop_publish_task_not_owned", "desktop publish task belongs to another user")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublishJobService) resolveRetryArticleID(ctx context.Context, tenantID int64, payload map[string]any) (int64, error) {
|
func (s *PublishJobService) resolveRetryArticleID(ctx context.Context, tenantID int64, payload map[string]any) (int64, error) {
|
||||||
if articleID, ok := resolveRetryArticleIDFromPayload(payload); ok {
|
if articleID, ok := resolveRetryArticleIDFromPayload(payload); ok {
|
||||||
return articleID, nil
|
return articleID, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user