feat(desktop): implement workspace foundation + desktop-client skeleton
Plan 0 (workspaces): add workspaces + workspace_memberships schema, extend JWT/Actor/claims with primary_workspace_id, seed default workspace per tenant, thread workspace_id through tenant monitoring quota. Plan A (desktop skeleton): new Electron app (apps/desktop-client) with main/ preload/renderer, shared Vue component package (packages/ui-shared), and server surface — desktop client registration + token rotation + heartbeat, SSE task event stream, desktop accounts/tasks/content handlers, publish job endpoint, and supporting repositories, services, sqlc queries, and migrations. Hard cutover per plan: remove browser-extension monitoring callback endpoints, stub legacy media API in admin-web, and delete monitoring_callback_handler.go.
This commit is contained in:
@@ -0,0 +1,381 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
|
||||
)
|
||||
|
||||
type DesktopPublishJob struct {
|
||||
DesktopID uuid.UUID
|
||||
TenantID int64
|
||||
WorkspaceID int64
|
||||
CreatedByUserID int64
|
||||
Title string
|
||||
ContentRef []byte
|
||||
ScheduledAt *time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateDesktopPublishJobParams struct {
|
||||
DesktopID uuid.UUID
|
||||
TenantID int64
|
||||
WorkspaceID int64
|
||||
CreatedByUserID int64
|
||||
Title string
|
||||
ContentRef []byte
|
||||
ScheduledAt *time.Time
|
||||
}
|
||||
|
||||
type DesktopTask struct {
|
||||
DesktopID uuid.UUID
|
||||
JobID uuid.UUID
|
||||
TenantID int64
|
||||
WorkspaceID int64
|
||||
TargetAccountID uuid.UUID
|
||||
TargetClientID uuid.UUID
|
||||
Platform string
|
||||
Kind string
|
||||
Payload []byte
|
||||
Status string
|
||||
DedupKey *string
|
||||
ActiveAttemptID *uuid.UUID
|
||||
LeaseExpiresAt *time.Time
|
||||
Attempts int
|
||||
ParkedReason *string
|
||||
Result []byte
|
||||
Error []byte
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateDesktopTaskParams struct {
|
||||
DesktopID uuid.UUID
|
||||
JobID uuid.UUID
|
||||
TenantID int64
|
||||
WorkspaceID int64
|
||||
TargetAccountID uuid.UUID
|
||||
TargetClientID uuid.UUID
|
||||
Platform string
|
||||
Kind string
|
||||
Payload []byte
|
||||
Status string
|
||||
DedupKey *string
|
||||
ParkedReason *string
|
||||
}
|
||||
|
||||
type DesktopTaskLeaseParams struct {
|
||||
AttemptID uuid.UUID
|
||||
LeaseTokenHash []byte
|
||||
}
|
||||
|
||||
type DesktopTaskAttempt struct {
|
||||
DesktopID uuid.UUID
|
||||
TaskID uuid.UUID
|
||||
ClientID uuid.UUID
|
||||
StartedAt time.Time
|
||||
EndedAt *time.Time
|
||||
FinalStatus *string
|
||||
Error []byte
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
type CreateDesktopTaskAttemptParams struct {
|
||||
DesktopID uuid.UUID
|
||||
TaskID uuid.UUID
|
||||
ClientID uuid.UUID
|
||||
LeaseTokenHash []byte
|
||||
}
|
||||
|
||||
type FinishDesktopTaskAttemptParams struct {
|
||||
TaskID uuid.UUID
|
||||
AttemptID uuid.UUID
|
||||
FinalStatus *string
|
||||
Error []byte
|
||||
}
|
||||
|
||||
type DesktopTaskRepository interface {
|
||||
CreatePublishJob(ctx context.Context, params CreateDesktopPublishJobParams) (*DesktopPublishJob, error)
|
||||
CreateTask(ctx context.Context, params CreateDesktopTaskParams) (*DesktopTask, error)
|
||||
GetByDesktopID(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopTask, error)
|
||||
LeaseNextQueued(ctx context.Context, clientID uuid.UUID, kind *string, params DesktopTaskLeaseParams) (*DesktopTask, error)
|
||||
LeaseQueuedByDesktopID(ctx context.Context, desktopID, clientID uuid.UUID, params DesktopTaskLeaseParams) (*DesktopTask, error)
|
||||
LeaseParked(ctx context.Context, desktopID, clientID uuid.UUID, params DesktopTaskLeaseParams) (*DesktopTask, error)
|
||||
ExtendLease(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte) (*DesktopTask, error)
|
||||
Park(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, parkedReason string) (*DesktopTask, error)
|
||||
Complete(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, status string, resultJSON, errorJSON []byte) (*DesktopTask, error)
|
||||
CancelByLease(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, errorJSON []byte) (*DesktopTask, error)
|
||||
CancelByClient(ctx context.Context, desktopID, clientID uuid.UUID, errorJSON []byte) (*DesktopTask, error)
|
||||
TenantCancel(ctx context.Context, desktopID uuid.UUID, workspaceID int64, errorJSON []byte) (*DesktopTask, error)
|
||||
Reconcile(ctx context.Context, desktopID uuid.UUID, workspaceID int64, status string, resultJSON, errorJSON []byte) (*DesktopTask, error)
|
||||
MarkUnknownByClient(ctx context.Context, clientID uuid.UUID, workspaceID int64, errorJSON []byte) (int64, error)
|
||||
CreateAttempt(ctx context.Context, params CreateDesktopTaskAttemptParams) (*DesktopTaskAttempt, error)
|
||||
FinishAttempt(ctx context.Context, params FinishDesktopTaskAttemptParams) error
|
||||
}
|
||||
|
||||
type desktopTaskRepository struct {
|
||||
q generated.Querier
|
||||
}
|
||||
|
||||
func NewDesktopTaskRepository(db generated.DBTX) DesktopTaskRepository {
|
||||
return &desktopTaskRepository{q: newQuerier(db)}
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) CreatePublishJob(ctx context.Context, params CreateDesktopPublishJobParams) (*DesktopPublishJob, error) {
|
||||
row, err := r.q.CreateDesktopPublishJob(ctx, generated.CreateDesktopPublishJobParams{
|
||||
DesktopID: pgUUID(params.DesktopID),
|
||||
TenantID: params.TenantID,
|
||||
WorkspaceID: params.WorkspaceID,
|
||||
CreatedByUserID: params.CreatedByUserID,
|
||||
Title: params.Title,
|
||||
ContentRef: params.ContentRef,
|
||||
ScheduledAt: pgTimestamp(params.ScheduledAt),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopPublishJobFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) CreateTask(ctx context.Context, params CreateDesktopTaskParams) (*DesktopTask, error) {
|
||||
row, err := r.q.CreateDesktopTask(ctx, generated.CreateDesktopTaskParams{
|
||||
DesktopID: pgUUID(params.DesktopID),
|
||||
JobID: pgUUID(params.JobID),
|
||||
TenantID: params.TenantID,
|
||||
WorkspaceID: params.WorkspaceID,
|
||||
TargetAccountID: pgUUID(params.TargetAccountID),
|
||||
TargetClientID: pgUUID(params.TargetClientID),
|
||||
PlatformID: params.Platform,
|
||||
Kind: params.Kind,
|
||||
Payload: params.Payload,
|
||||
Status: params.Status,
|
||||
DedupKey: pgText(params.DedupKey),
|
||||
ParkedReason: pgText(params.ParkedReason),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) GetByDesktopID(ctx context.Context, desktopID uuid.UUID, workspaceID int64) (*DesktopTask, error) {
|
||||
row, err := r.q.GetDesktopTaskByDesktopID(ctx, generated.GetDesktopTaskByDesktopIDParams{
|
||||
DesktopID: pgUUID(desktopID),
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) LeaseNextQueued(ctx context.Context, clientID uuid.UUID, kind *string, params DesktopTaskLeaseParams) (*DesktopTask, error) {
|
||||
row, err := r.q.LeaseNextQueuedDesktopTask(ctx, generated.LeaseNextQueuedDesktopTaskParams{
|
||||
AttemptID: pgUUID(params.AttemptID),
|
||||
LeaseTokenHash: params.LeaseTokenHash,
|
||||
ClientID: pgUUID(clientID),
|
||||
Kind: pgText(kind),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) LeaseQueuedByDesktopID(ctx context.Context, desktopID, clientID uuid.UUID, params DesktopTaskLeaseParams) (*DesktopTask, error) {
|
||||
row, err := r.q.LeaseQueuedDesktopTaskByDesktopID(ctx, generated.LeaseQueuedDesktopTaskByDesktopIDParams{
|
||||
AttemptID: pgUUID(params.AttemptID),
|
||||
LeaseTokenHash: params.LeaseTokenHash,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
ClientID: pgUUID(clientID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) LeaseParked(ctx context.Context, desktopID, clientID uuid.UUID, params DesktopTaskLeaseParams) (*DesktopTask, error) {
|
||||
row, err := r.q.LeaseParkedDesktopTask(ctx, generated.LeaseParkedDesktopTaskParams{
|
||||
AttemptID: pgUUID(params.AttemptID),
|
||||
LeaseTokenHash: params.LeaseTokenHash,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
ClientID: pgUUID(clientID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) ExtendLease(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.ExtendDesktopTaskLease(ctx, generated.ExtendDesktopTaskLeaseParams{
|
||||
DesktopID: pgUUID(desktopID),
|
||||
LeaseTokenHash: leaseTokenHash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) Park(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, parkedReason string) (*DesktopTask, error) {
|
||||
row, err := r.q.ParkDesktopTask(ctx, generated.ParkDesktopTaskParams{
|
||||
ParkedReason: pgText(&parkedReason),
|
||||
DesktopID: pgUUID(desktopID),
|
||||
LeaseTokenHash: leaseTokenHash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) Complete(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, status string, resultJSON, errorJSON []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.CompleteDesktopTask(ctx, generated.CompleteDesktopTaskParams{
|
||||
Status: status,
|
||||
Result: resultJSON,
|
||||
Error: errorJSON,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
LeaseTokenHash: leaseTokenHash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) CancelByLease(ctx context.Context, desktopID uuid.UUID, leaseTokenHash []byte, errorJSON []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.CancelDesktopTaskByLease(ctx, generated.CancelDesktopTaskByLeaseParams{
|
||||
Error: errorJSON,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
LeaseTokenHash: leaseTokenHash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) CancelByClient(ctx context.Context, desktopID, clientID uuid.UUID, errorJSON []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.CancelDesktopTaskByClient(ctx, generated.CancelDesktopTaskByClientParams{
|
||||
Error: errorJSON,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
ClientID: pgUUID(clientID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) TenantCancel(ctx context.Context, desktopID uuid.UUID, workspaceID int64, errorJSON []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.TenantCancelDesktopTask(ctx, generated.TenantCancelDesktopTaskParams{
|
||||
Error: errorJSON,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) Reconcile(ctx context.Context, desktopID uuid.UUID, workspaceID int64, status string, resultJSON, errorJSON []byte) (*DesktopTask, error) {
|
||||
row, err := r.q.ReconcileDesktopTask(ctx, generated.ReconcileDesktopTaskParams{
|
||||
Status: status,
|
||||
Result: resultJSON,
|
||||
Error: errorJSON,
|
||||
DesktopID: pgUUID(desktopID),
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) MarkUnknownByClient(ctx context.Context, clientID uuid.UUID, workspaceID int64, errorJSON []byte) (int64, error) {
|
||||
return r.q.MarkDesktopTasksUnknownByClient(ctx, generated.MarkDesktopTasksUnknownByClientParams{
|
||||
Error: errorJSON,
|
||||
ClientID: pgUUID(clientID),
|
||||
WorkspaceID: workspaceID,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) CreateAttempt(ctx context.Context, params CreateDesktopTaskAttemptParams) (*DesktopTaskAttempt, error) {
|
||||
row, err := r.q.CreateDesktopTaskAttempt(ctx, generated.CreateDesktopTaskAttemptParams{
|
||||
DesktopID: pgUUID(params.DesktopID),
|
||||
TaskID: pgUUID(params.TaskID),
|
||||
ClientID: pgUUID(params.ClientID),
|
||||
LeaseTokenHash: params.LeaseTokenHash,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return desktopTaskAttemptFromGenerated(row), nil
|
||||
}
|
||||
|
||||
func (r *desktopTaskRepository) FinishAttempt(ctx context.Context, params FinishDesktopTaskAttemptParams) error {
|
||||
return r.q.FinishDesktopTaskAttempt(ctx, generated.FinishDesktopTaskAttemptParams{
|
||||
FinalStatus: pgText(params.FinalStatus),
|
||||
Error: params.Error,
|
||||
TaskID: pgUUID(params.TaskID),
|
||||
AttemptID: pgUUID(params.AttemptID),
|
||||
})
|
||||
}
|
||||
|
||||
func desktopPublishJobFromGenerated(row generated.DesktopPublishJob) *DesktopPublishJob {
|
||||
return &DesktopPublishJob{
|
||||
DesktopID: uuidFromPG(row.DesktopID),
|
||||
TenantID: row.TenantID,
|
||||
WorkspaceID: row.WorkspaceID,
|
||||
CreatedByUserID: row.CreatedByUserID,
|
||||
Title: row.Title,
|
||||
ContentRef: row.ContentRef,
|
||||
ScheduledAt: optionalTime(row.ScheduledAt),
|
||||
CreatedAt: timeFromTimestamp(row.CreatedAt),
|
||||
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func desktopTaskFromGenerated(row generated.DesktopTask) *DesktopTask {
|
||||
return &DesktopTask{
|
||||
DesktopID: uuidFromPG(row.DesktopID),
|
||||
JobID: uuidFromPG(row.JobID),
|
||||
TenantID: row.TenantID,
|
||||
WorkspaceID: row.WorkspaceID,
|
||||
TargetAccountID: uuidFromPG(row.TargetAccountID),
|
||||
TargetClientID: uuidFromPG(row.TargetClientID),
|
||||
Platform: row.PlatformID,
|
||||
Kind: row.Kind,
|
||||
Payload: row.Payload,
|
||||
Status: row.Status,
|
||||
DedupKey: nullableText(row.DedupKey),
|
||||
ActiveAttemptID: nullableUUID(row.ActiveAttemptID),
|
||||
LeaseExpiresAt: optionalTime(row.LeaseExpiresAt),
|
||||
Attempts: int(row.Attempts),
|
||||
ParkedReason: nullableText(row.ParkedReason),
|
||||
Result: row.Result,
|
||||
Error: row.Error,
|
||||
CreatedAt: timeFromTimestamp(row.CreatedAt),
|
||||
UpdatedAt: timeFromTimestamp(row.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
func desktopTaskAttemptFromGenerated(row generated.DesktopTaskAttempt) *DesktopTaskAttempt {
|
||||
return &DesktopTaskAttempt{
|
||||
DesktopID: uuidFromPG(row.DesktopID),
|
||||
TaskID: uuidFromPG(row.TaskID),
|
||||
ClientID: uuidFromPG(row.ClientID),
|
||||
StartedAt: timeFromTimestamp(row.StartedAt),
|
||||
EndedAt: optionalTime(row.EndedAt),
|
||||
FinalStatus: nullableText(row.FinalStatus),
|
||||
Error: row.Error,
|
||||
CreatedAt: timeFromTimestamp(row.CreatedAt),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user