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 文档同步口径。
This commit is contained in:
@@ -6,10 +6,12 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
goredis "github.com/redis/go-redis/v9"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/auth"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
@@ -19,14 +21,16 @@ import (
|
||||
const DesktopClientTokenTTL = 30 * 24 * time.Hour
|
||||
|
||||
type DesktopClientService struct {
|
||||
repo repository.DesktopClientRepository
|
||||
repo repository.DesktopClientRepository
|
||||
redis *goredis.Client
|
||||
}
|
||||
|
||||
func NewDesktopClientService(repo repository.DesktopClientRepository) *DesktopClientService {
|
||||
return &DesktopClientService{repo: repo}
|
||||
func NewDesktopClientService(repo repository.DesktopClientRepository, redis *goredis.Client) *DesktopClientService {
|
||||
return &DesktopClientService{repo: repo, redis: redis}
|
||||
}
|
||||
|
||||
type RegisterDesktopClientRequest struct {
|
||||
ClientID string `json:"client_id"`
|
||||
DeviceName string `json:"device_name" binding:"required"`
|
||||
OS string `json:"os" binding:"required"`
|
||||
CPUArch string `json:"cpu_arch"`
|
||||
@@ -35,11 +39,12 @@ type RegisterDesktopClientRequest struct {
|
||||
}
|
||||
|
||||
type HeartbeatDesktopClientRequest struct {
|
||||
DeviceName *string `json:"device_name"`
|
||||
OS *string `json:"os"`
|
||||
CPUArch *string `json:"cpu_arch"`
|
||||
ClientVersion *string `json:"client_version"`
|
||||
Channel *string `json:"channel"`
|
||||
DeviceName *string `json:"device_name"`
|
||||
OS *string `json:"os"`
|
||||
CPUArch *string `json:"cpu_arch"`
|
||||
ClientVersion *string `json:"client_version"`
|
||||
Channel *string `json:"channel"`
|
||||
AccountIDs []string `json:"account_ids"`
|
||||
}
|
||||
|
||||
type DesktopClientView struct {
|
||||
@@ -76,6 +81,10 @@ type HeartbeatDesktopClientResponse struct {
|
||||
ServerTime time.Time `json:"server_time"`
|
||||
}
|
||||
|
||||
type OfflineDesktopClientResponse struct {
|
||||
ServerTime time.Time `json:"server_time"`
|
||||
}
|
||||
|
||||
func HashDesktopClientToken(raw string) []byte {
|
||||
sum := sha256.Sum256([]byte(raw))
|
||||
return sum[:]
|
||||
@@ -91,8 +100,9 @@ func (s *DesktopClientService) Register(ctx context.Context, actor auth.Actor, r
|
||||
return nil, response.ErrInternal(50071, "desktop_client_token_failed", "failed to generate desktop client token")
|
||||
}
|
||||
|
||||
clientID := resolveRegisterClientID(req.ClientID)
|
||||
client, err := s.repo.Register(ctx, repository.RegisterDesktopClientParams{
|
||||
ID: uuid.New(),
|
||||
ID: clientID,
|
||||
TenantID: actor.TenantID,
|
||||
WorkspaceID: actor.PrimaryWorkspaceID,
|
||||
UserID: actor.UserID,
|
||||
@@ -107,6 +117,8 @@ func (s *DesktopClientService) Register(ctx context.Context, actor auth.Actor, r
|
||||
return nil, response.ErrInternal(50072, "desktop_client_register_failed", "failed to persist desktop client")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, client.ID)
|
||||
|
||||
return &RegisterDesktopClientResponse{
|
||||
ClientID: client.ID.String(),
|
||||
ClientToken: rawToken,
|
||||
@@ -137,6 +149,8 @@ func (s *DesktopClientService) Rotate(ctx context.Context, client *repository.De
|
||||
return nil, response.ErrInternal(50074, "desktop_client_rotate_failed", "failed to rotate desktop client token")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, updated.ID)
|
||||
|
||||
return &RotateDesktopClientResponse{
|
||||
ClientToken: rawToken,
|
||||
ExpiresAt: time.Now().Add(DesktopClientTokenTTL).Unix(),
|
||||
@@ -165,6 +179,9 @@ func (s *DesktopClientService) Heartbeat(ctx context.Context, client *repository
|
||||
return nil, response.ErrInternal(50075, "desktop_client_heartbeat_failed", "failed to persist desktop client heartbeat")
|
||||
}
|
||||
|
||||
markDesktopClientPresent(ctx, s.redis, updated.ID)
|
||||
markDesktopAccountsPresent(ctx, s.redis, updated.ID, parseDesktopAccountIDs(req.AccountIDs))
|
||||
|
||||
return &HeartbeatDesktopClientResponse{
|
||||
Client: buildDesktopClientView(updated),
|
||||
ServerTime: time.Now().UTC(),
|
||||
@@ -184,10 +201,24 @@ func (s *DesktopClientService) Revoke(ctx context.Context, client *repository.De
|
||||
return nil, response.ErrInternal(50076, "desktop_client_revoke_failed", "failed to revoke desktop client")
|
||||
}
|
||||
|
||||
clearDesktopClientPresenceState(ctx, s.redis, updated.ID)
|
||||
|
||||
view := buildDesktopClientView(updated)
|
||||
return &view, nil
|
||||
}
|
||||
|
||||
func (s *DesktopClientService) Offline(ctx context.Context, client *repository.DesktopClient) (*OfflineDesktopClientResponse, error) {
|
||||
if client == nil {
|
||||
return nil, response.ErrUnauthorized(40108, "desktop_client_missing", "desktop client context is required")
|
||||
}
|
||||
|
||||
clearDesktopClientPresenceState(ctx, s.redis, client.ID)
|
||||
|
||||
return &OfflineDesktopClientResponse{
|
||||
ServerTime: time.Now().UTC(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newDesktopClientToken() (string, []byte, error) {
|
||||
raw := make([]byte, 32)
|
||||
if _, err := rand.Read(raw); err != nil {
|
||||
@@ -227,3 +258,27 @@ func trimmedStringPtr(value string) *string {
|
||||
trimmed := value
|
||||
return &trimmed
|
||||
}
|
||||
|
||||
func parseDesktopAccountIDs(values []string) []uuid.UUID {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
accountIDs := make([]uuid.UUID, 0, len(values))
|
||||
for _, value := range values {
|
||||
parsed, err := uuid.Parse(strings.TrimSpace(value))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
accountIDs = append(accountIDs, parsed)
|
||||
}
|
||||
return accountIDs
|
||||
}
|
||||
|
||||
func resolveRegisterClientID(value string) uuid.UUID {
|
||||
parsed, err := uuid.Parse(strings.TrimSpace(value))
|
||||
if err == nil && parsed != uuid.Nil {
|
||||
return parsed
|
||||
}
|
||||
return uuid.New()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user