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
@@ -119,6 +119,81 @@ type Competitor struct {
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
}
type DesktopClient struct {
ID pgtype.UUID `json:"id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
UserID int64 `json:"user_id"`
TokenHash []byte `json:"token_hash"`
DeviceName pgtype.Text `json:"device_name"`
Os pgtype.Text `json:"os"`
CpuArch pgtype.Text `json:"cpu_arch"`
ClientVersion pgtype.Text `json:"client_version"`
Channel pgtype.Text `json:"channel"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
LastSeenAt pgtype.Timestamptz `json:"last_seen_at"`
LastRotatedAt pgtype.Timestamptz `json:"last_rotated_at"`
RevokedAt pgtype.Timestamptz `json:"revoked_at"`
}
type DesktopPublishJob struct {
ID int64 `json:"id"`
DesktopID pgtype.UUID `json:"desktop_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
CreatedByUserID int64 `json:"created_by_user_id"`
Title string `json:"title"`
ContentRef []byte `json:"content_ref"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type DesktopTask struct {
ID int64 `json:"id"`
DesktopID pgtype.UUID `json:"desktop_id"`
JobID pgtype.UUID `json:"job_id"`
TenantID int64 `json:"tenant_id"`
WorkspaceID int64 `json:"workspace_id"`
TargetAccountID pgtype.UUID `json:"target_account_id"`
TargetClientID pgtype.UUID `json:"target_client_id"`
PlatformID string `json:"platform_id"`
Kind string `json:"kind"`
Payload []byte `json:"payload"`
Status string `json:"status"`
DedupKey pgtype.Text `json:"dedup_key"`
ActiveAttemptID pgtype.UUID `json:"active_attempt_id"`
LeaseTokenHash []byte `json:"lease_token_hash"`
LeaseExpiresAt pgtype.Timestamptz `json:"lease_expires_at"`
Attempts int32 `json:"attempts"`
ParkedReason pgtype.Text `json:"parked_reason"`
Result []byte `json:"result"`
Error []byte `json:"error"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type DesktopTaskAttempt struct {
ID int64 `json:"id"`
DesktopID pgtype.UUID `json:"desktop_id"`
TaskID pgtype.UUID `json:"task_id"`
ClientID pgtype.UUID `json:"client_id"`
LeaseTokenHash []byte `json:"lease_token_hash"`
StartedAt pgtype.Timestamptz `json:"started_at"`
EndedAt pgtype.Timestamptz `json:"ended_at"`
FinalStatus pgtype.Text `json:"final_status"`
Error []byte `json:"error"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type DesktopTaskTrace struct {
TaskID pgtype.UUID `json:"task_id"`
AttemptID pgtype.UUID `json:"attempt_id"`
Size pgtype.Int8 `json:"size"`
UploadedAt pgtype.Timestamptz `json:"uploaded_at"`
ObjectKey pgtype.Text `json:"object_key"`
}
type GenerationTask struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
@@ -376,19 +451,29 @@ type Plan struct {
}
type PlatformAccount struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
UserID int64 `json:"user_id"`
PlatformID string `json:"platform_id"`
PlatformUid string `json:"platform_uid"`
Nickname string `json:"nickname"`
AvatarUrl pgtype.Text `json:"avatar_url"`
Status string `json:"status"`
MetadataJson []byte `json:"metadata_json"`
LastCheckAt pgtype.Timestamptz `json:"last_check_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
UserID int64 `json:"user_id"`
PlatformID string `json:"platform_id"`
PlatformUid string `json:"platform_uid"`
Nickname string `json:"nickname"`
AvatarUrl pgtype.Text `json:"avatar_url"`
Status string `json:"status"`
MetadataJson []byte `json:"metadata_json"`
LastCheckAt pgtype.Timestamptz `json:"last_check_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
WorkspaceID int64 `json:"workspace_id"`
DesktopID pgtype.UUID `json:"desktop_id"`
ClientID pgtype.UUID `json:"client_id"`
AccountFingerprint pgtype.Text `json:"account_fingerprint"`
DisplayName string `json:"display_name"`
Health string `json:"health"`
VerifiedAt pgtype.Timestamptz `json:"verified_at"`
Tags []byte `json:"tags"`
SyncVersion int64 `json:"sync_version"`
DeleteRequestedAt pgtype.Timestamptz `json:"delete_requested_at"`
}
type PlatformUserRole struct {
@@ -602,6 +687,7 @@ type TenantMonitoringQuota struct {
PlanTier string `json:"plan_tier"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
WorkspaceID int64 `json:"workspace_id"`
}
type TenantPlanSubscription struct {
@@ -641,3 +727,23 @@ type User struct {
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
}
type Workspace struct {
ID int64 `json:"id"`
TenantID int64 `json:"tenant_id"`
Name string `json:"name"`
Slug string `json:"slug"`
IsDefault bool `json:"is_default"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type WorkspaceMembership struct {
ID int64 `json:"id"`
WorkspaceID int64 `json:"workspace_id"`
UserID int64 `json:"user_id"`
TenantID int64 `json:"tenant_id"`
Role string `json:"role"`
IsPrimary bool `json:"is_primary"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}