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,287 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.30.0
|
||||
// source: desktop_client.sql
|
||||
|
||||
package generated
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const getDesktopClientByTokenHash = `-- name: GetDesktopClientByTokenHash :one
|
||||
SELECT id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
FROM desktop_clients
|
||||
WHERE token_hash = $1
|
||||
AND revoked_at IS NULL
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetDesktopClientByTokenHash(ctx context.Context, tokenHash []byte) (DesktopClient, error) {
|
||||
row := q.db.QueryRow(ctx, getDesktopClientByTokenHash, tokenHash)
|
||||
var i DesktopClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const heartbeatDesktopClient = `-- name: HeartbeatDesktopClient :one
|
||||
UPDATE desktop_clients
|
||||
SET device_name = COALESCE($1, device_name),
|
||||
os = COALESCE($2, os),
|
||||
cpu_arch = COALESCE($3, cpu_arch),
|
||||
client_version = COALESCE($4, client_version),
|
||||
channel = COALESCE($5, channel),
|
||||
last_seen_at = now()
|
||||
WHERE id = $6
|
||||
AND workspace_id = $7
|
||||
AND revoked_at IS NULL
|
||||
RETURNING id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
`
|
||||
|
||||
type HeartbeatDesktopClientParams struct {
|
||||
DeviceName pgtype.Text `json:"device_name"`
|
||||
Os pgtype.Text `json:"os"`
|
||||
CpuArch pgtype.Text `json:"cpu_arch"`
|
||||
ClientVersion pgtype.Text `json:"client_version"`
|
||||
Channel pgtype.Text `json:"channel"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) HeartbeatDesktopClient(ctx context.Context, arg HeartbeatDesktopClientParams) (DesktopClient, error) {
|
||||
row := q.db.QueryRow(ctx, heartbeatDesktopClient,
|
||||
arg.DeviceName,
|
||||
arg.Os,
|
||||
arg.CpuArch,
|
||||
arg.ClientVersion,
|
||||
arg.Channel,
|
||||
arg.ID,
|
||||
arg.WorkspaceID,
|
||||
)
|
||||
var i DesktopClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listDesktopClientsByWorkspace = `-- name: ListDesktopClientsByWorkspace :many
|
||||
SELECT id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
FROM desktop_clients
|
||||
WHERE workspace_id = $1
|
||||
AND revoked_at IS NULL
|
||||
ORDER BY last_seen_at DESC NULLS LAST, created_at DESC
|
||||
`
|
||||
|
||||
func (q *Queries) ListDesktopClientsByWorkspace(ctx context.Context, workspaceID int64) ([]DesktopClient, error) {
|
||||
rows, err := q.db.Query(ctx, listDesktopClientsByWorkspace, workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []DesktopClient
|
||||
for rows.Next() {
|
||||
var i DesktopClient
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const registerDesktopClient = `-- name: RegisterDesktopClient :one
|
||||
INSERT INTO desktop_clients (
|
||||
id,
|
||||
tenant_id,
|
||||
workspace_id,
|
||||
user_id,
|
||||
token_hash,
|
||||
device_name,
|
||||
os,
|
||||
cpu_arch,
|
||||
client_version,
|
||||
channel
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10
|
||||
)
|
||||
RETURNING id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
`
|
||||
|
||||
type RegisterDesktopClientParams struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
TokenHash []byte `json:"token_hash"`
|
||||
DeviceName pgtype.Text `json:"device_name"`
|
||||
Os pgtype.Text `json:"os"`
|
||||
CpuArch pgtype.Text `json:"cpu_arch"`
|
||||
ClientVersion pgtype.Text `json:"client_version"`
|
||||
Channel pgtype.Text `json:"channel"`
|
||||
}
|
||||
|
||||
func (q *Queries) RegisterDesktopClient(ctx context.Context, arg RegisterDesktopClientParams) (DesktopClient, error) {
|
||||
row := q.db.QueryRow(ctx, registerDesktopClient,
|
||||
arg.ID,
|
||||
arg.TenantID,
|
||||
arg.WorkspaceID,
|
||||
arg.UserID,
|
||||
arg.TokenHash,
|
||||
arg.DeviceName,
|
||||
arg.Os,
|
||||
arg.CpuArch,
|
||||
arg.ClientVersion,
|
||||
arg.Channel,
|
||||
)
|
||||
var i DesktopClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const revokeDesktopClient = `-- name: RevokeDesktopClient :one
|
||||
UPDATE desktop_clients
|
||||
SET revoked_at = now()
|
||||
WHERE id = $1
|
||||
AND workspace_id = $2
|
||||
AND revoked_at IS NULL
|
||||
RETURNING id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
`
|
||||
|
||||
type RevokeDesktopClientParams struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) RevokeDesktopClient(ctx context.Context, arg RevokeDesktopClientParams) (DesktopClient, error) {
|
||||
row := q.db.QueryRow(ctx, revokeDesktopClient, arg.ID, arg.WorkspaceID)
|
||||
var i DesktopClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const rotateDesktopClientToken = `-- name: RotateDesktopClientToken :one
|
||||
UPDATE desktop_clients
|
||||
SET token_hash = $1,
|
||||
last_rotated_at = now()
|
||||
WHERE id = $2
|
||||
AND workspace_id = $3
|
||||
AND revoked_at IS NULL
|
||||
RETURNING id, tenant_id, workspace_id, user_id, token_hash, device_name, os, cpu_arch, client_version, channel, created_at, last_seen_at, last_rotated_at, revoked_at
|
||||
`
|
||||
|
||||
type RotateDesktopClientTokenParams struct {
|
||||
TokenHash []byte `json:"token_hash"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WorkspaceID int64 `json:"workspace_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) RotateDesktopClientToken(ctx context.Context, arg RotateDesktopClientTokenParams) (DesktopClient, error) {
|
||||
row := q.db.QueryRow(ctx, rotateDesktopClientToken, arg.TokenHash, arg.ID, arg.WorkspaceID)
|
||||
var i DesktopClient
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.TenantID,
|
||||
&i.WorkspaceID,
|
||||
&i.UserID,
|
||||
&i.TokenHash,
|
||||
&i.DeviceName,
|
||||
&i.Os,
|
||||
&i.CpuArch,
|
||||
&i.ClientVersion,
|
||||
&i.Channel,
|
||||
&i.CreatedAt,
|
||||
&i.LastSeenAt,
|
||||
&i.LastRotatedAt,
|
||||
&i.RevokedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
Reference in New Issue
Block a user