Files
geo/server/internal/tenant/repository/desktop_account_repo.go
T
root a617d39a4a feat(desktop): drop parked review flow, add publish management
SaaS 侧人工审核后才创建 publish job,desktop 不再做二次审核:移除
manual/waiting_user/parked/from_parked 状态机与 LeaseFromParked 查询,
desktop client 只执行发布并新增"发布管理"页(待发布队列 / 历史 / 再次
发送)。同时抽离 @geo/publisher-platforms 共享适配器包、新增 Redis-based
desktop_presence 与 publish_record_support,刷新 admin-web 发布弹窗与
媒体库;plan A / spec 文档同步口径。
2026-04-20 09:52:48 +08:00

473 lines
12 KiB
Go

package repository
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
)
type DesktopAccount struct {
DesktopID uuid.UUID
TenantID int64
WorkspaceID int64
UserID int64
ClientID *uuid.UUID
Platform string
PlatformUID string
AccountFingerprint *string
DisplayName string
AvatarURL *string
Health string
VerifiedAt *time.Time
Tags []string
SyncVersion int64
DeletedAt *time.Time
DeleteRequestedAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type UpsertDesktopAccountParams struct {
DesktopID uuid.UUID
TenantID int64
WorkspaceID int64
UserID int64
ClientID uuid.UUID
Platform string
PlatformUID string
AccountFingerprint *string
DisplayName string
AvatarURL *string
Health string
VerifiedAt *time.Time
Tags []string
IfSyncVersion *int64
}
type PatchDesktopAccountParams struct {
DesktopID uuid.UUID
WorkspaceID int64
DisplayName *string
Health *string
VerifiedAt *time.Time
Tags *[]string
IfSyncVersion int64
}
type DesktopAccountRepository interface {
ListByUser(ctx context.Context, workspaceID, userID int64) ([]DesktopAccount, error)
GetByDesktopID(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error)
GetByIdentity(ctx context.Context, workspaceID int64, platform, platformUID string) (*DesktopAccount, error)
Upsert(ctx context.Context, params UpsertDesktopAccountParams) (*DesktopAccount, error)
Patch(ctx context.Context, params PatchDesktopAccountParams) (*DesktopAccount, error)
Tombstone(ctx context.Context, desktopID uuid.UUID, workspaceID, ifSyncVersion int64) (*DesktopAccount, error)
MarkDeleteRequested(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error)
ClearDeleteRequested(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error)
}
type desktopAccountRepository struct {
q generated.Querier
}
func NewDesktopAccountRepository(db generated.DBTX) DesktopAccountRepository {
return &desktopAccountRepository{q: newQuerier(db)}
}
func (r *desktopAccountRepository) ListByUser(ctx context.Context, workspaceID, userID int64) ([]DesktopAccount, error) {
rows, err := r.q.ListDesktopAccountsByUser(ctx, generated.ListDesktopAccountsByUserParams{
WorkspaceID: workspaceID,
UserID: userID,
})
if err != nil {
return nil, err
}
items := make([]DesktopAccount, 0, len(rows))
for _, row := range rows {
item, err := desktopAccountFromListRow(row)
if err != nil {
return nil, err
}
items = append(items, *item)
}
return items, nil
}
func (r *desktopAccountRepository) GetByDesktopID(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error) {
row, err := r.q.GetDesktopAccountByDesktopID(ctx, generated.GetDesktopAccountByDesktopIDParams{
DesktopID: pgUUID(desktopID),
WorkspaceID: workspaceID,
})
if err != nil {
return nil, err
}
return desktopAccountFromGetRow(row)
}
func (r *desktopAccountRepository) GetByIdentity(ctx context.Context, workspaceID int64, platform, platformUID string) (*DesktopAccount, error) {
row, err := r.q.GetDesktopAccountByIdentity(ctx, generated.GetDesktopAccountByIdentityParams{
WorkspaceID: workspaceID,
PlatformID: platform,
PlatformUid: platformUID,
})
if err != nil {
return nil, err
}
return desktopAccountFromIdentityRow(row)
}
func (r *desktopAccountRepository) Upsert(ctx context.Context, params UpsertDesktopAccountParams) (*DesktopAccount, error) {
tagsJSON, err := marshalJSON(params.Tags)
if err != nil {
return nil, fmt.Errorf("marshal tags: %w", err)
}
var metadataJSON []byte
if len(tagsJSON) > 0 {
metadataJSON, err = marshalJSON(map[string]any{"tags": params.Tags})
if err != nil {
return nil, fmt.Errorf("marshal metadata: %w", err)
}
}
row, err := r.q.UpsertDesktopAccount(ctx, generated.UpsertDesktopAccountParams{
DesktopID: pgUUID(params.DesktopID),
TenantID: params.TenantID,
WorkspaceID: params.WorkspaceID,
UserID: params.UserID,
ClientID: pgUUID(params.ClientID),
PlatformID: params.Platform,
PlatformUid: params.PlatformUID,
DisplayName: params.DisplayName,
AvatarUrl: pgText(params.AvatarURL),
LegacyStatus: legacyStatusFromHealth(params.Health),
MetadataJson: metadataJSON,
VerifiedAt: pgTimestamp(params.VerifiedAt),
AccountFingerprint: pgText(params.AccountFingerprint),
Health: params.Health,
Tags: tagsJSON,
IfSyncVersion: pgInt8(params.IfSyncVersion),
})
if err != nil {
return nil, err
}
return desktopAccountFromUpsertRow(row)
}
func (r *desktopAccountRepository) Patch(ctx context.Context, params PatchDesktopAccountParams) (*DesktopAccount, error) {
var tagsJSON []byte
var err error
if params.Tags != nil {
tagsJSON, err = marshalJSON(*params.Tags)
if err != nil {
return nil, fmt.Errorf("marshal tags: %w", err)
}
}
row, err := r.q.PatchDesktopAccount(ctx, generated.PatchDesktopAccountParams{
DisplayName: pgText(params.DisplayName),
Health: pgText(params.Health),
LegacyStatus: pgText(legacyStatusPtrFromHealth(params.Health)),
VerifiedAt: pgTimestamp(params.VerifiedAt),
Tags: tagsJSON,
DesktopID: pgUUID(params.DesktopID),
WorkspaceID: params.WorkspaceID,
IfSyncVersion: params.IfSyncVersion,
})
if err != nil {
return nil, err
}
return desktopAccountFromPatchRow(row)
}
func (r *desktopAccountRepository) Tombstone(ctx context.Context, desktopID uuid.UUID, workspaceID, ifSyncVersion int64) (*DesktopAccount, error) {
row, err := r.q.TombstoneDesktopAccount(ctx, generated.TombstoneDesktopAccountParams{
DesktopID: pgUUID(desktopID),
WorkspaceID: workspaceID,
IfSyncVersion: ifSyncVersion,
})
if err != nil {
return nil, err
}
return desktopAccountFromTombstoneRow(row)
}
func (r *desktopAccountRepository) MarkDeleteRequested(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error) {
row, err := r.q.MarkDesktopAccountDeleteRequested(ctx, generated.MarkDesktopAccountDeleteRequestedParams{
DesktopID: pgUUID(desktopID),
WorkspaceID: workspaceID,
})
if err != nil {
return nil, err
}
return desktopAccountFromMarkDeleteRow(row)
}
func (r *desktopAccountRepository) ClearDeleteRequested(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopAccount, error) {
row, err := r.q.ClearDesktopAccountDeleteRequested(ctx, generated.ClearDesktopAccountDeleteRequestedParams{
DesktopID: pgUUID(desktopID),
WorkspaceID: workspaceID,
})
if err != nil {
return nil, err
}
return desktopAccountFromClearDeleteRow(row)
}
func desktopAccountFromListRow(row generated.ListDesktopAccountsByUserRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromGetRow(row generated.GetDesktopAccountByDesktopIDRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromIdentityRow(row generated.GetDesktopAccountByIdentityRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromUpsertRow(row generated.UpsertDesktopAccountRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromPatchRow(row generated.PatchDesktopAccountRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromTombstoneRow(row generated.TombstoneDesktopAccountRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromMarkDeleteRow(row generated.MarkDesktopAccountDeleteRequestedRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func desktopAccountFromClearDeleteRow(row generated.ClearDesktopAccountDeleteRequestedRow) (*DesktopAccount, error) {
return buildDesktopAccount(
row.DesktopID,
row.TenantID,
row.WorkspaceID,
row.UserID,
row.ClientID,
row.PlatformID,
row.PlatformUid,
row.AccountFingerprint,
row.DisplayName,
row.AvatarUrl,
row.Health,
row.VerifiedAt,
row.Tags,
row.SyncVersion,
row.DeletedAt,
row.DeleteRequestedAt,
row.CreatedAt,
row.UpdatedAt,
)
}
func buildDesktopAccount(
desktopID pgtype.UUID,
tenantID int64,
workspaceID int64,
userID int64,
clientID pgtype.UUID,
platform string,
platformUID string,
accountFingerprint pgtype.Text,
displayName string,
avatarURL pgtype.Text,
health string,
verifiedAt pgtype.Timestamptz,
tags []byte,
syncVersion int64,
deletedAt pgtype.Timestamptz,
deleteRequestedAt pgtype.Timestamptz,
createdAt pgtype.Timestamptz,
updatedAt pgtype.Timestamptz,
) (*DesktopAccount, error) {
tagList, err := unmarshalStringSlice(tags)
if err != nil {
return nil, fmt.Errorf("unmarshal desktop account tags: %w", err)
}
return &DesktopAccount{
DesktopID: uuidFromPG(desktopID),
TenantID: tenantID,
WorkspaceID: workspaceID,
UserID: userID,
ClientID: nullableUUID(clientID),
Platform: platform,
PlatformUID: platformUID,
AccountFingerprint: nullableText(accountFingerprint),
DisplayName: displayName,
AvatarURL: nullableText(avatarURL),
Health: health,
VerifiedAt: optionalTime(verifiedAt),
Tags: tagList,
SyncVersion: syncVersion,
DeletedAt: optionalTime(deletedAt),
DeleteRequestedAt: optionalTime(deleteRequestedAt),
CreatedAt: timeFromTimestamp(createdAt),
UpdatedAt: timeFromTimestamp(updatedAt),
}, nil
}
func legacyStatusFromHealth(health string) string {
switch health {
case "live":
return "active"
case "captcha":
return "captcha"
case "risk":
return "risk"
default:
return "expired"
}
}
func legacyStatusPtrFromHealth(health *string) *string {
if health == nil {
return nil
}
value := legacyStatusFromHealth(*health)
return &value
}