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:
@@ -314,11 +314,16 @@ func (s *MediaService) RegisterPluginInstallation(ctx context.Context, req Regis
|
||||
func ensureMonitoringPrimaryInstallation(ctx context.Context, tx pgx.Tx, tenantID, installationID int64) error {
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO tenant_monitoring_quotas (
|
||||
tenant_id, primary_installation_id
|
||||
tenant_id, workspace_id, primary_installation_id
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
(SELECT id FROM workspaces WHERE tenant_id = $1 AND is_default = TRUE LIMIT 1),
|
||||
$2
|
||||
)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT (tenant_id) DO UPDATE
|
||||
SET primary_installation_id = COALESCE(tenant_monitoring_quotas.primary_installation_id, EXCLUDED.primary_installation_id),
|
||||
SET workspace_id = COALESCE(tenant_monitoring_quotas.workspace_id, EXCLUDED.workspace_id),
|
||||
primary_installation_id = COALESCE(tenant_monitoring_quotas.primary_installation_id, EXCLUDED.primary_installation_id),
|
||||
updated_at = NOW()
|
||||
`, tenantID, installationID); err != nil {
|
||||
return response.ErrInternal(50068, "monitoring_quota_update_failed", "failed to assign monitoring plugin installation")
|
||||
@@ -652,7 +657,11 @@ func (s *MediaService) HandleBindCallback(ctx context.Context, req PluginBindCal
|
||||
err = tx.QueryRow(ctx, `
|
||||
SELECT id
|
||||
FROM platform_accounts
|
||||
WHERE tenant_id = $1 AND platform_id = $2 AND platform_uid = $3 AND deleted_at IS NULL
|
||||
WHERE tenant_id = $1
|
||||
AND workspace_id = (SELECT id FROM workspaces WHERE tenant_id = $1 AND is_default = TRUE LIMIT 1)
|
||||
AND platform_id = $2
|
||||
AND platform_uid = $3
|
||||
AND deleted_at IS NULL
|
||||
LIMIT 1
|
||||
`, session.TenantID, req.PlatformID, req.PlatformUID).Scan(&platformAccountID)
|
||||
|
||||
@@ -660,10 +669,29 @@ func (s *MediaService) HandleBindCallback(ctx context.Context, req PluginBindCal
|
||||
case errors.Is(err, pgx.ErrNoRows):
|
||||
if err := tx.QueryRow(ctx, `
|
||||
INSERT INTO platform_accounts (
|
||||
tenant_id, user_id, platform_id, platform_uid, nickname, avatar_url, status,
|
||||
metadata_json, last_check_at
|
||||
tenant_id, workspace_id, user_id, platform_id, platform_uid, nickname, avatar_url, status,
|
||||
metadata_json, last_check_at, desktop_id, client_id, account_fingerprint,
|
||||
display_name, health, verified_at, tags, sync_version
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
(SELECT id FROM workspaces WHERE tenant_id = $1 AND is_default = TRUE LIMIT 1),
|
||||
$2, $3, $4, $5, $6, $7, $8, NOW(),
|
||||
gen_random_uuid(), NULL, NULL,
|
||||
$5,
|
||||
CASE
|
||||
WHEN $7 = 'active' THEN 'live'
|
||||
WHEN $7 = 'captcha' THEN 'captcha'
|
||||
WHEN $7 = 'risk' THEN 'risk'
|
||||
ELSE 'expired'
|
||||
END,
|
||||
NOW(),
|
||||
CASE
|
||||
WHEN $8 IS NOT NULL AND jsonb_typeof($8 -> 'tags') = 'array' THEN $8 -> 'tags'
|
||||
ELSE NULL
|
||||
END,
|
||||
1
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())
|
||||
RETURNING id
|
||||
`, session.TenantID, session.UserID, req.PlatformID, req.PlatformUID, req.Nickname, req.AvatarURL, status, metadataJSON).Scan(&platformAccountID); err != nil {
|
||||
return nil, response.ErrInternal(50045, "platform_account_insert_failed", "failed to create platform account")
|
||||
@@ -675,10 +703,23 @@ func (s *MediaService) HandleBindCallback(ctx context.Context, req PluginBindCal
|
||||
UPDATE platform_accounts
|
||||
SET user_id = $1,
|
||||
nickname = $2,
|
||||
display_name = $2,
|
||||
avatar_url = $3,
|
||||
status = $4,
|
||||
health = CASE
|
||||
WHEN $4 = 'active' THEN 'live'
|
||||
WHEN $4 = 'captcha' THEN 'captcha'
|
||||
WHEN $4 = 'risk' THEN 'risk'
|
||||
ELSE 'expired'
|
||||
END,
|
||||
metadata_json = $5,
|
||||
last_check_at = NOW(),
|
||||
verified_at = NOW(),
|
||||
tags = CASE
|
||||
WHEN $5 IS NOT NULL AND jsonb_typeof($5 -> 'tags') = 'array' THEN $5 -> 'tags'
|
||||
ELSE tags
|
||||
END,
|
||||
sync_version = sync_version + 1,
|
||||
updated_at = NOW()
|
||||
WHERE id = $6
|
||||
`, session.UserID, req.Nickname, req.AvatarURL, status, metadataJSON, platformAccountID); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user