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
+53 -45
View File
@@ -11,31 +11,31 @@ import (
"strings"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"golang.org/x/crypto/bcrypt"
sharedauth "github.com/geo-platform/tenant-api/internal/shared/auth"
"github.com/geo-platform/tenant-api/internal/shared/config"
"github.com/geo-platform/tenant-api/internal/tenant/prompts"
"github.com/geo-platform/tenant-api/internal/tenant/repository"
)
type seedState struct {
TenantID int64
UserID int64
WorkspaceID int64
TestTenantID int64
TestUserID int64
PlanID int64
BrandID int64
KeywordPrimaryID int64
KeywordSecondaryID int64
QuestionPrimaryID int64
QuestionSecondaryID int64
QuestionTertiaryID int64
ArticleID int64
PluginInstallationID int64
TenantID int64
UserID int64
WorkspaceID int64
TestTenantID int64
TestUserID int64
PlanID int64
BrandID int64
KeywordPrimaryID int64
KeywordSecondaryID int64
QuestionPrimaryID int64
QuestionSecondaryID int64
QuestionTertiaryID int64
ArticleID int64
DesktopClientID uuid.UUID
}
type monitoringRunSeed struct {
@@ -137,7 +137,7 @@ func main() {
fmt.Printf(" Test User ID: %d (test@geo.local / Test@123)\n", state.TestUserID)
fmt.Printf(" Plan ID: %d (free)\n", state.PlanID)
fmt.Printf(" Brand ID: %d (轻氧口腔)\n", state.BrandID)
fmt.Printf(" Plugin Installation ID: %d\n", state.PluginInstallationID)
fmt.Printf(" Desktop Client ID: %s\n", state.DesktopClientID.String())
fmt.Println(" Templates: 4 platform presets")
fmt.Println(" Monitoring: brand daily snapshots + question runs + citation facts")
}
@@ -241,7 +241,7 @@ func seedBusinessData(ctx context.Context, pool *pgxpool.Pool, membershipCfg con
return nil, err
}
state.PluginInstallationID, err = ensurePluginInstallation(ctx, tx, state.TenantID, state.UserID)
state.DesktopClientID, err = ensureDesktopClient(ctx, tx, state.TenantID, state.WorkspaceID, state.UserID)
if err != nil {
return nil, err
}
@@ -641,47 +641,55 @@ func ensureArticle(ctx context.Context, tx pgx.Tx, tenantID int64) (int64, error
return articleID, nil
}
func ensurePluginInstallation(ctx context.Context, tx pgx.Tx, tenantID, userID int64) (int64, error) {
installationKey := "demo-monitoring-installation"
tokenHash := sharedauth.HashToken("demo-monitoring-installation-token")
func ensureDesktopClient(ctx context.Context, tx pgx.Tx, tenantID, workspaceID, userID int64) (uuid.UUID, error) {
clientID := uuid.NewSHA1(uuid.NameSpaceOID, []byte("geo-rankly-demo-monitoring-client"))
tokenHash := sha256.Sum256([]byte("demo-monitoring-client-token"))
var installationID int64
var ensuredID uuid.UUID
err := tx.QueryRow(ctx, `
WITH ensured AS (
INSERT INTO plugin_installations (
tenant_id, user_id, installation_key, installation_name, browser_name,
client_version, installation_token_hash, status, last_seen_at
INSERT INTO desktop_clients (
id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, last_seen_at
)
VALUES (
$1, $2, $3, 'GEO Monitoring Chrome', 'Chrome',
'0.1.0', $4, 'active', NOW()
$1, $2, $3, $4, $5, 'GEO Monitoring Desktop', 'macOS', 'arm64', '0.1.0', 'stable', NOW()
)
ON CONFLICT DO NOTHING
ON CONFLICT (id) DO UPDATE
SET tenant_id = EXCLUDED.tenant_id,
workspace_id = EXCLUDED.workspace_id,
user_id = EXCLUDED.user_id,
token_hash = EXCLUDED.token_hash,
device_name = EXCLUDED.device_name,
os = EXCLUDED.os,
cpu_arch = EXCLUDED.cpu_arch,
client_version = EXCLUDED.client_version,
channel = EXCLUDED.channel,
last_seen_at = NOW(),
revoked_at = NULL
RETURNING id
)
SELECT id FROM ensured
UNION ALL
SELECT id
FROM plugin_installations
WHERE tenant_id = $1 AND installation_key = $3 AND deleted_at IS NULL
FROM desktop_clients
WHERE id = $1
LIMIT 1
`, tenantID, userID, installationKey, tokenHash).Scan(&installationID)
`, clientID, tenantID, workspaceID, userID, tokenHash[:]).Scan(&ensuredID)
if err != nil {
return 0, wrapSeedErr("insert plugin installation", err)
return uuid.Nil, wrapSeedErr("insert desktop client", err)
}
_, err = tx.Exec(ctx, `
UPDATE plugin_installations
SET status = 'active',
installation_token_hash = $2,
UPDATE desktop_clients
SET token_hash = $2,
last_seen_at = NOW(),
updated_at = NOW()
revoked_at = NULL
WHERE id = $1
`, installationID, tokenHash)
`, ensuredID, tokenHash[:])
if err != nil {
return 0, wrapSeedErr("touch plugin installation", err)
return uuid.Nil, wrapSeedErr("touch desktop client", err)
}
return installationID, nil
return ensuredID, nil
}
func ensureMonitoringQuota(ctx context.Context, tx pgx.Tx, state *seedState) error {
@@ -689,20 +697,20 @@ func ensureMonitoringQuota(ctx context.Context, tx pgx.Tx, state *seedState) err
_, err := tx.Exec(ctx, `
INSERT INTO tenant_monitoring_quotas (
tenant_id, workspace_id, max_brands, collection_mode, question_selection_mode, collect_frequency,
enabled_platforms, primary_installation_id, task_daily_hard_cap, plan_tier
enabled_platforms, primary_client_id, task_daily_hard_cap, plan_tier
)
VALUES ($1, $2, 1, 'plugin', 'best_effort_all', 'daily', $3::jsonb, $4, 36, 'free')
VALUES ($1, $2, 1, 'desktop', 'best_effort_all', 'daily', $3::jsonb, $4, 36, 'free')
ON CONFLICT (tenant_id) DO UPDATE
SET workspace_id = COALESCE(tenant_monitoring_quotas.workspace_id, EXCLUDED.workspace_id),
SET workspace_id = EXCLUDED.workspace_id,
collection_mode = EXCLUDED.collection_mode,
question_selection_mode = EXCLUDED.question_selection_mode,
collect_frequency = EXCLUDED.collect_frequency,
enabled_platforms = EXCLUDED.enabled_platforms,
primary_installation_id = EXCLUDED.primary_installation_id,
primary_client_id = EXCLUDED.primary_client_id,
task_daily_hard_cap = EXCLUDED.task_daily_hard_cap,
plan_tier = EXCLUDED.plan_tier,
updated_at = NOW()
`, state.TenantID, state.WorkspaceID, string(enabledPlatforms), state.PluginInstallationID)
`, state.TenantID, state.WorkspaceID, string(enabledPlatforms), state.DesktopClientID)
return wrapSeedErr("upsert monitoring quota", err)
}
@@ -799,11 +807,11 @@ func insertPlatformAccessSnapshots(ctx context.Context, tx pgx.Tx, state *seedSt
for _, item := range entries {
if _, err := tx.Exec(ctx, `
INSERT INTO monitoring_platform_access_snapshots (
tenant_id, installation_id, ai_platform_id, business_date, access_status,
tenant_id, client_id, ai_platform_id, business_date, access_status,
connected, accessible, detected_at, reason_code, reason_message
)
VALUES ($1, $2, $3, $4::date, $5, $6, $7, NOW(), $8, $9)
`, state.TenantID, state.PluginInstallationID, item.PlatformID, businessDate, item.AccessStatus, item.Connected, item.Accessible, item.ReasonCode, item.ReasonMessage); err != nil {
`, state.TenantID, state.DesktopClientID, item.PlatformID, businessDate, item.AccessStatus, item.Connected, item.Accessible, item.ReasonCode, item.ReasonMessage); err != nil {
return wrapSeedErr("insert platform access snapshot", err)
}
}