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 }