feat(desktop): implement workspace foundation + desktop-client skeleton

Plan 0 (workspaces): add workspaces + workspace_memberships schema, extend
JWT/Actor/claims with primary_workspace_id, seed default workspace per tenant,
thread workspace_id through tenant monitoring quota.

Plan A (desktop skeleton): new Electron app (apps/desktop-client) with main/
preload/renderer, shared Vue component package (packages/ui-shared), and server
surface — desktop client registration + token rotation + heartbeat, SSE task
event stream, desktop accounts/tasks/content handlers, publish job endpoint,
and supporting repositories, services, sqlc queries, and migrations.

Hard cutover per plan: remove browser-extension monitoring callback endpoints,
stub legacy media API in admin-web, and delete monitoring_callback_handler.go.
This commit is contained in:
2026-04-19 14:18:20 +08:00
parent 98f9e95875
commit b16e9f0bd1
141 changed files with 21533 additions and 357 deletions
@@ -10,6 +10,10 @@ import (
type Querier interface {
ApproveKolSubscription(ctx context.Context, arg ApproveKolSubscriptionParams) error
CancelDesktopTaskByClient(ctx context.Context, arg CancelDesktopTaskByClientParams) (DesktopTask, error)
CancelDesktopTaskByLease(ctx context.Context, arg CancelDesktopTaskByLeaseParams) (DesktopTask, error)
ClearDesktopAccountDeleteRequested(ctx context.Context, arg ClearDesktopAccountDeleteRequestedParams) (ClearDesktopAccountDeleteRequestedRow, error)
CompleteDesktopTask(ctx context.Context, arg CompleteDesktopTaskParams) (DesktopTask, error)
ConfirmReservation(ctx context.Context, arg ConfirmReservationParams) error
CountActiveImagesInFolder(ctx context.Context, arg CountActiveImagesInFolderParams) (int32, error)
CountArticles(ctx context.Context, arg CountArticlesParams) (int64, error)
@@ -27,6 +31,9 @@ type Querier interface {
CreateAuditLog(ctx context.Context, arg CreateAuditLogParams) error
CreateBrand(ctx context.Context, arg CreateBrandParams) (CreateBrandRow, error)
CreateCompetitor(ctx context.Context, arg CreateCompetitorParams) (CreateCompetitorRow, error)
CreateDesktopPublishJob(ctx context.Context, arg CreateDesktopPublishJobParams) (DesktopPublishJob, error)
CreateDesktopTask(ctx context.Context, arg CreateDesktopTaskParams) (DesktopTask, error)
CreateDesktopTaskAttempt(ctx context.Context, arg CreateDesktopTaskAttemptParams) (DesktopTaskAttempt, error)
CreateGenerationTask(ctx context.Context, arg CreateGenerationTaskParams) (int64, error)
CreateImageFolder(ctx context.Context, arg CreateImageFolderParams) (int64, error)
CreateKeyword(ctx context.Context, arg CreateKeywordParams) (CreateKeywordRow, error)
@@ -47,12 +54,18 @@ type Querier interface {
DeleteImageReferencesByArticle(ctx context.Context, arg DeleteImageReferencesByArticleParams) error
DeleteImageReferencesByArticleScope(ctx context.Context, arg DeleteImageReferencesByArticleScopeParams) error
EnsureImageStorageUsageRow(ctx context.Context, tenantID int64) error
ExtendDesktopTaskLease(ctx context.Context, arg ExtendDesktopTaskLeaseParams) (DesktopTask, error)
FinishDesktopTaskAttempt(ctx context.Context, arg FinishDesktopTaskAttemptParams) error
GetActiveKolSubscription(ctx context.Context, arg GetActiveKolSubscriptionParams) (KolSubscription, error)
GetActivePlanForTenant(ctx context.Context, tenantID int64) (GetActivePlanForTenantRow, error)
GetArticleByID(ctx context.Context, arg GetArticleByIDParams) (GetArticleByIDRow, error)
GetArticleVersions(ctx context.Context, articleID int64) ([]GetArticleVersionsRow, error)
GetBrandByID(ctx context.Context, arg GetBrandByIDParams) (GetBrandByIDRow, error)
GetCurrentBalance(ctx context.Context, arg GetCurrentBalanceParams) (int32, error)
GetDesktopAccountByDesktopID(ctx context.Context, arg GetDesktopAccountByDesktopIDParams) (GetDesktopAccountByDesktopIDRow, error)
GetDesktopAccountByIdentity(ctx context.Context, arg GetDesktopAccountByIdentityParams) (GetDesktopAccountByIdentityRow, error)
GetDesktopClientByTokenHash(ctx context.Context, tokenHash []byte) (DesktopClient, error)
GetDesktopTaskByDesktopID(ctx context.Context, arg GetDesktopTaskByDesktopIDParams) (DesktopTask, error)
GetImageAsset(ctx context.Context, arg GetImageAssetParams) (GetImageAssetRow, error)
GetImageFolder(ctx context.Context, arg GetImageFolderParams) (GetImageFolderRow, error)
GetImageStorageUsage(ctx context.Context, tenantID int64) (GetImageStorageUsageRow, error)
@@ -78,6 +91,7 @@ type Querier interface {
GetTenantMembershipByTenantAndUser(ctx context.Context, arg GetTenantMembershipByTenantAndUserParams) (GetTenantMembershipByTenantAndUserRow, error)
GetUserByEmail(ctx context.Context, email string) (GetUserByEmailRow, error)
GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error)
HeartbeatDesktopClient(ctx context.Context, arg HeartbeatDesktopClientParams) (DesktopClient, error)
InsertImageAsset(ctx context.Context, arg InsertImageAssetParams) (int64, error)
InsertQuotaLedger(ctx context.Context, arg InsertQuotaLedgerParams) (int64, error)
// Cross-tenant aggregation: KOL owns packages across subscriber tenants.
@@ -86,6 +100,9 @@ type Querier interface {
KolDashboardTrend(ctx context.Context, arg KolDashboardTrendParams) ([]KolDashboardTrendRow, error)
// Cross-tenant aggregation for KOL dashboard.
KolUsageCountByPackage(ctx context.Context, packageIds []int64) ([]KolUsageCountByPackageRow, error)
LeaseNextQueuedDesktopTask(ctx context.Context, arg LeaseNextQueuedDesktopTaskParams) (DesktopTask, error)
LeaseParkedDesktopTask(ctx context.Context, arg LeaseParkedDesktopTaskParams) (DesktopTask, error)
LeaseQueuedDesktopTaskByDesktopID(ctx context.Context, arg LeaseQueuedDesktopTaskByDesktopIDParams) (DesktopTask, error)
ListActiveImagesByFolder(ctx context.Context, arg ListActiveImagesByFolderParams) ([]ListActiveImagesByFolderRow, error)
ListActiveKolPromptsByPackage(ctx context.Context, arg ListActiveKolPromptsByPackageParams) ([]ListActiveKolPromptsByPackageRow, error)
// Cross-tenant read: used by platform admin for subscription fan-out.
@@ -95,6 +112,8 @@ type Querier interface {
ListArticles(ctx context.Context, arg ListArticlesParams) ([]ListArticlesRow, error)
ListBrands(ctx context.Context, tenantID int64) ([]ListBrandsRow, error)
ListCompetitors(ctx context.Context, arg ListCompetitorsParams) ([]ListCompetitorsRow, error)
ListDesktopAccountsByClient(ctx context.Context, arg ListDesktopAccountsByClientParams) ([]ListDesktopAccountsByClientRow, error)
ListDesktopClientsByWorkspace(ctx context.Context, workspaceID int64) ([]DesktopClient, error)
ListImageAssets(ctx context.Context, arg ListImageAssetsParams) ([]ListImageAssetsRow, error)
ListImageFolders(ctx context.Context, tenantID int64) ([]ListImageFoldersRow, error)
ListImageReferenceArticles(ctx context.Context, arg ListImageReferenceArticlesParams) ([]ListImageReferenceArticlesRow, error)
@@ -122,6 +141,8 @@ type Querier interface {
ListScheduleTasks(ctx context.Context, arg ListScheduleTasksParams) ([]ListScheduleTasksRow, error)
ListSubscriptionPromptsByTenant(ctx context.Context, tenantID int64) ([]ListSubscriptionPromptsByTenantRow, error)
ListTemplates(ctx context.Context, tenantID int64) ([]ListTemplatesRow, error)
MarkDesktopAccountDeleteRequested(ctx context.Context, arg MarkDesktopAccountDeleteRequestedParams) (MarkDesktopAccountDeleteRequestedRow, error)
MarkDesktopTasksUnknownByClient(ctx context.Context, arg MarkDesktopTasksUnknownByClientParams) (int64, error)
MarkImagesInFolderPendingDelete(ctx context.Context, arg MarkImagesInFolderPendingDeleteParams) error
MarkKolAssistCompleted(ctx context.Context, arg MarkKolAssistCompletedParams) error
MarkKolAssistFailed(ctx context.Context, arg MarkKolAssistFailedParams) error
@@ -131,11 +152,17 @@ type Querier interface {
MarkKolUsageFailed(ctx context.Context, arg MarkKolUsageFailedParams) error
MarkKolUsageFailedByTask(ctx context.Context, arg MarkKolUsageFailedByTaskParams) error
NextKolPromptRevisionNo(ctx context.Context, arg NextKolPromptRevisionNoParams) (int32, error)
ParkDesktopTask(ctx context.Context, arg ParkDesktopTaskParams) (DesktopTask, error)
PatchDesktopAccount(ctx context.Context, arg PatchDesktopAccountParams) (PatchDesktopAccountRow, error)
ReconcileDesktopTask(ctx context.Context, arg ReconcileDesktopTaskParams) (DesktopTask, error)
RefundReservation(ctx context.Context, arg RefundReservationParams) error
RegisterDesktopClient(ctx context.Context, arg RegisterDesktopClientParams) (DesktopClient, error)
ReleaseImageStorageQuota(ctx context.Context, arg ReleaseImageStorageQuotaParams) error
RevokeDesktopClient(ctx context.Context, arg RevokeDesktopClientParams) (DesktopClient, error)
RevokeKolSubscription(ctx context.Context, arg RevokeKolSubscriptionParams) error
RevokeSubscriptionPromptsByPrompt(ctx context.Context, arg RevokeSubscriptionPromptsByPromptParams) error
RevokeSubscriptionPromptsBySubscription(ctx context.Context, arg RevokeSubscriptionPromptsBySubscriptionParams) error
RotateDesktopClientToken(ctx context.Context, arg RotateDesktopClientTokenParams) (DesktopClient, error)
SoftDeleteArticle(ctx context.Context, arg SoftDeleteArticleParams) error
SoftDeleteBrand(ctx context.Context, arg SoftDeleteBrandParams) error
SoftDeleteCompetitor(ctx context.Context, arg SoftDeleteCompetitorParams) error
@@ -149,6 +176,8 @@ type Querier interface {
SoftDeleteQuestion(ctx context.Context, arg SoftDeleteQuestionParams) error
SoftDeleteQuestionsByBrand(ctx context.Context, arg SoftDeleteQuestionsByBrandParams) error
SoftDeleteScheduleTask(ctx context.Context, arg SoftDeleteScheduleTaskParams) error
TenantCancelDesktopTask(ctx context.Context, arg TenantCancelDesktopTaskParams) (DesktopTask, error)
TombstoneDesktopAccount(ctx context.Context, arg TombstoneDesktopAccountParams) (TombstoneDesktopAccountRow, error)
UpdateArticleCurrentVersion(ctx context.Context, arg UpdateArticleCurrentVersionParams) error
UpdateArticleGenerateStatus(ctx context.Context, arg UpdateArticleGenerateStatusParams) error
UpdateBrand(ctx context.Context, arg UpdateBrandParams) error
@@ -175,6 +204,7 @@ type Querier interface {
UpdateScheduleTask(ctx context.Context, arg UpdateScheduleTaskParams) error
UpdateScheduleTaskStatus(ctx context.Context, arg UpdateScheduleTaskStatusParams) error
UpdateTaskRecordStatus(ctx context.Context, arg UpdateTaskRecordStatusParams) error
UpsertDesktopAccount(ctx context.Context, arg UpsertDesktopAccountParams) (UpsertDesktopAccountRow, error)
UpsertImageReference(ctx context.Context, arg UpsertImageReferenceParams) error
}