refactor(tenant): drop legacy plugin_installations, migrate monitoring to desktop_clients

Hard cutover from the browser-extension plugin flow to desktop clients:
remove plugin_installations/plugin_sessions tables and related service,
handler, router, and generated model code; migrate monitoring quotas
and collector types to desktop_clients (UUID primary_client_id);
recreate platform_access_snapshots keyed by client_id; update dev-seed
and callback types accordingly; mark legacy design docs as historical.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-20 13:52:35 +08:00
parent f5a1cd81b2
commit 5ff2e2e74c
25 changed files with 535 additions and 1384 deletions
@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
@@ -20,7 +21,7 @@ import (
const (
defaultTrackingDays = 7
maxTrackingDays = 30
monitoringCollectorType = "plugin"
monitoringCollectorType = "desktop"
monitoringOnlineDuration = 20 * time.Minute
monitoringCollectNowQuestionLimit = 5
)
@@ -189,9 +190,9 @@ type MonitoringCollectNowResponse struct {
}
type monitoringQuotaConfig struct {
CollectionMode string
PrimaryInstallationID *int64
EnabledPlatforms []string
CollectionMode string
PrimaryClientID *uuid.UUID
EnabledPlatforms []string
}
type monitoringBrandIdentity struct {
@@ -301,7 +302,7 @@ func (s *MonitoringService) DashboardComposite(
return nil, err
}
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryInstallationID, endDate)
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryClientID, endDate)
if err != nil {
return nil, err
}
@@ -378,7 +379,7 @@ func (s *MonitoringService) QuestionDetail(ctx context.Context, brandID, questio
}
}
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryInstallationID, toDate)
accessStates, err := s.loadAccessStates(ctx, actor.TenantID, quota.PrimaryClientID, toDate)
if err != nil {
return nil, err
}
@@ -497,15 +498,15 @@ func (s *MonitoringService) CollectNow(ctx context.Context, brandID int64, keywo
platforms := resolveMonitoringPlatforms(quota.EnabledPlatforms)
questionIDs := configuredQuestionIDs(configuredQuestions)
installationID, err := s.resolveCollectNowInstallationID(ctx, actor.TenantID, quota.PrimaryInstallationID)
clientID, err := s.resolveCollectNowClientID(ctx, actor.TenantID, actor.PrimaryWorkspaceID, quota.PrimaryClientID)
if err != nil {
return nil, err
}
if installationID == nil {
return nil, response.ErrConflict(40941, "installation_offline", "primary monitoring installation is offline")
if clientID == nil {
return nil, response.ErrConflict(40941, "desktop_client_offline", "primary monitoring desktop client is offline")
}
if err := s.ensureInstallationOnline(ctx, actor.TenantID, *installationID); err != nil {
if err := s.ensureClientOnline(ctx, actor.TenantID, actor.PrimaryWorkspaceID, *clientID); err != nil {
return nil, err
}
@@ -584,9 +585,9 @@ func (s *MonitoringService) CollectNow(ctx context.Context, brandID int64, keywo
}, nil
}
func (s *MonitoringService) resolveCollectNowInstallationID(ctx context.Context, tenantID int64, preferred *int64) (*int64, error) {
func (s *MonitoringService) resolveCollectNowClientID(ctx context.Context, tenantID, workspaceID int64, preferred *uuid.UUID) (*uuid.UUID, error) {
if preferred != nil {
online, err := s.isInstallationOnline(ctx, tenantID, *preferred)
online, err := s.isClientOnline(ctx, tenantID, workspaceID, *preferred)
if err != nil {
return nil, err
}
@@ -595,7 +596,7 @@ func (s *MonitoringService) resolveCollectNowInstallationID(ctx context.Context,
}
}
fallbackID, err := s.findLatestOnlineInstallation(ctx, tenantID)
fallbackID, err := s.findLatestOnlineClient(ctx, tenantID, workspaceID)
if err != nil {
return nil, err
}
@@ -604,7 +605,7 @@ func (s *MonitoringService) resolveCollectNowInstallationID(ctx context.Context,
}
if preferred == nil || *preferred != *fallbackID {
if err := s.persistPrimaryInstallationID(ctx, tenantID, *fallbackID); err != nil {
if err := s.persistPrimaryClientID(ctx, tenantID, workspaceID, *fallbackID); err != nil {
return nil, err
}
}
@@ -612,44 +613,44 @@ func (s *MonitoringService) resolveCollectNowInstallationID(ctx context.Context,
return fallbackID, nil
}
func (s *MonitoringService) findLatestOnlineInstallation(ctx context.Context, tenantID int64) (*int64, error) {
var installationID int64
func (s *MonitoringService) findLatestOnlineClient(ctx context.Context, tenantID, workspaceID int64) (*uuid.UUID, error) {
var clientID uuid.UUID
err := s.businessPool.QueryRow(ctx, `
SELECT id
FROM plugin_installations
FROM desktop_clients
WHERE tenant_id = $1
AND status = 'active'
AND deleted_at IS NULL
AND workspace_id = $2
AND revoked_at IS NULL
AND last_seen_at IS NOT NULL
AND last_seen_at >= NOW() - $2::interval
ORDER BY last_seen_at DESC, updated_at DESC, id DESC
AND last_seen_at >= NOW() - $3::interval
ORDER BY last_seen_at DESC, created_at DESC, id DESC
LIMIT 1
`, tenantID, formatPgInterval(monitoringOnlineDuration)).Scan(&installationID)
`, tenantID, workspaceID, formatPgInterval(monitoringOnlineDuration)).Scan(&clientID)
if err != nil {
if err == pgx.ErrNoRows {
return nil, nil
}
return nil, response.ErrInternal(50041, "query_failed", "failed to inspect online monitoring installation")
return nil, response.ErrInternal(50041, "query_failed", "failed to inspect online monitoring desktop client")
}
return &installationID, nil
return &clientID, nil
}
func (s *MonitoringService) persistPrimaryInstallationID(ctx context.Context, tenantID, installationID int64) error {
func (s *MonitoringService) persistPrimaryClientID(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) error {
if _, err := s.businessPool.Exec(ctx, `
INSERT INTO tenant_monitoring_quotas (
tenant_id, workspace_id, primary_installation_id
tenant_id, workspace_id, primary_client_id
)
VALUES (
$1,
(SELECT id FROM workspaces WHERE tenant_id = $1 AND is_default = TRUE LIMIT 1),
$2
$2,
$3
)
ON CONFLICT (tenant_id) DO UPDATE
SET workspace_id = COALESCE(tenant_monitoring_quotas.workspace_id, EXCLUDED.workspace_id),
primary_installation_id = EXCLUDED.primary_installation_id,
SET workspace_id = EXCLUDED.workspace_id,
primary_client_id = EXCLUDED.primary_client_id,
updated_at = NOW()
`, tenantID, installationID); err != nil {
return response.ErrInternal(50041, "update_failed", "failed to persist primary monitoring installation")
`, tenantID, workspaceID, clientID); err != nil {
return response.ErrInternal(50041, "update_failed", "failed to persist primary monitoring desktop client")
}
return nil
}
@@ -894,12 +895,12 @@ func (s *MonitoringService) loadQuota(ctx context.Context, tenantID int64) (moni
}
var enabledJSON []byte
var installationID sql.NullInt64
var clientID uuid.NullUUID
err := s.businessPool.QueryRow(ctx, `
SELECT collection_mode, enabled_platforms, primary_installation_id
SELECT collection_mode, enabled_platforms, primary_client_id
FROM tenant_monitoring_quotas
WHERE tenant_id = $1
`, tenantID).Scan(&cfg.CollectionMode, &enabledJSON, &installationID)
`, tenantID).Scan(&cfg.CollectionMode, &enabledJSON, &clientID)
if err != nil {
if err == pgx.ErrNoRows {
return cfg, nil
@@ -913,9 +914,9 @@ func (s *MonitoringService) loadQuota(ctx context.Context, tenantID int64) (moni
cfg.EnabledPlatforms = enabled
}
}
if installationID.Valid {
id := installationID.Int64
cfg.PrimaryInstallationID = &id
if clientID.Valid {
id := clientID.UUID
cfg.PrimaryClientID = &id
}
if strings.TrimSpace(cfg.CollectionMode) == "" {
cfg.CollectionMode = monitoringCollectorType
@@ -1700,9 +1701,9 @@ func (s *MonitoringService) loadTimeBucketsForPlatform(
return buckets, nil
}
func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64, installationID *int64, businessDate time.Time) (map[string]monitoringAccessState, error) {
func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64, clientID *uuid.UUID, businessDate time.Time) (map[string]monitoringAccessState, error) {
states := map[string]monitoringAccessState{}
if installationID == nil {
if clientID == nil {
return states, nil
}
@@ -1710,9 +1711,9 @@ func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64
SELECT ai_platform_id, access_status
FROM monitoring_platform_access_snapshots
WHERE tenant_id = $1
AND installation_id = $2
AND client_id = $2
AND business_date = $3::date
`, tenantID, *installationID, businessDate.Format("2006-01-02"))
`, tenantID, *clientID, businessDate.Format("2006-01-02"))
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load platform access states")
}
@@ -2452,32 +2453,32 @@ func (s *MonitoringService) loadQuestionContentCitations(ctx context.Context, te
return items, nil
}
func (s *MonitoringService) ensureInstallationOnline(ctx context.Context, tenantID, installationID int64) error {
online, err := s.isInstallationOnline(ctx, tenantID, installationID)
func (s *MonitoringService) ensureClientOnline(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) error {
online, err := s.isClientOnline(ctx, tenantID, workspaceID, clientID)
if err != nil {
return err
}
if !online {
return response.ErrConflict(40941, "installation_offline", "primary monitoring installation is offline")
return response.ErrConflict(40941, "desktop_client_offline", "primary monitoring desktop client is offline")
}
return nil
}
func (s *MonitoringService) isInstallationOnline(ctx context.Context, tenantID, installationID int64) (bool, error) {
func (s *MonitoringService) isClientOnline(ctx context.Context, tenantID, workspaceID int64, clientID uuid.UUID) (bool, error) {
var lastSeenAt sql.NullTime
err := s.businessPool.QueryRow(ctx, `
SELECT last_seen_at
FROM plugin_installations
FROM desktop_clients
WHERE id = $1
AND tenant_id = $2
AND status = 'active'
AND deleted_at IS NULL
`, installationID, tenantID).Scan(&lastSeenAt)
AND workspace_id = $3
AND revoked_at IS NULL
`, clientID, tenantID, workspaceID).Scan(&lastSeenAt)
if err != nil {
if err == pgx.ErrNoRows {
return false, nil
}
return false, response.ErrInternal(50041, "query_failed", "failed to inspect monitoring installation")
return false, response.ErrInternal(50041, "query_failed", "failed to inspect monitoring desktop client")
}
if !lastSeenAt.Valid || time.Since(lastSeenAt.Time) > monitoringOnlineDuration {