feat(server/desktop): ingest desktop account health reports

Add a POST /desktop/accounts/health-reports endpoint that buffers reports in
Redis, dedupes via signature, and publishes change events to a new
desktop.account.health RabbitMQ queue. A sink worker drains the queue, and
the account service overlays runtime health onto database health when serving
desktop accounts. Publish job creation now consults runtime health so a stale
DB row no longer blocks publishing once the desktop client reports it healthy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 21:33:36 +08:00
parent 94f185c3a1
commit 051976e4a9
10 changed files with 647 additions and 51 deletions
@@ -81,6 +81,16 @@ func (s *PublishJobService) Create(ctx context.Context, actor auth.Actor, req Cr
return nil, err
}
requestedAccountIDs := make([]uuid.UUID, 0, len(req.Accounts))
for _, accountReq := range req.Accounts {
accountID, parseErr := uuid.Parse(strings.TrimSpace(accountReq.AccountID))
if parseErr != nil {
return nil, response.ErrBadRequest(40092, "invalid_desktop_account_id", "account_id must be a uuid")
}
requestedAccountIDs = append(requestedAccountIDs, accountID)
}
runtimeHealth := loadDesktopAccountRuntimeHealth(ctx, s.redis, actor.PrimaryWorkspaceID, requestedAccountIDs)
tx, err := s.pool.Begin(ctx)
if err != nil {
return nil, response.ErrInternal(50096, "desktop_publish_job_begin_failed", "failed to start desktop publish job transaction")
@@ -101,12 +111,7 @@ func (s *PublishJobService) Create(ctx context.Context, actor auth.Actor, req Cr
}
targets := make([]publishTarget, 0, len(req.Accounts))
for _, accountReq := range req.Accounts {
accountID, parseErr := uuid.Parse(strings.TrimSpace(accountReq.AccountID))
if parseErr != nil {
return nil, response.ErrBadRequest(40092, "invalid_desktop_account_id", "account_id must be a uuid")
}
for _, accountID := range requestedAccountIDs {
account, lookupErr := accountRepo.GetByDesktopID(ctx, accountID, actor.PrimaryWorkspaceID)
if lookupErr != nil {
if errors.Is(lookupErr, pgx.ErrNoRows) {
@@ -114,7 +119,11 @@ func (s *PublishJobService) Create(ctx context.Context, actor auth.Actor, req Cr
}
return nil, response.ErrInternal(50098, "desktop_account_lookup_failed", "failed to load selected desktop account")
}
if account.Health != "live" {
accountHealth := account.Health
if report, ok := runtimeHealth[account.DesktopID]; ok && strings.TrimSpace(report.Health) != "" {
accountHealth = report.Health
}
if accountHealth != "live" {
return nil, response.ErrConflict(40993, "desktop_account_not_publishable", "one or more selected desktop accounts require re-authorization before publishing")
}
targetClientID := s.resolveTargetClientID(ctx, account)