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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user