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,66 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/auditlog"
|
||||
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/objectstorage"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/response"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type DesktopContentService struct {
|
||||
articles *ArticleService
|
||||
}
|
||||
|
||||
type DesktopArticleContentView struct {
|
||||
ArticleID int64 `json:"article_id"`
|
||||
Title string `json:"title"`
|
||||
HTMLContent *string `json:"html_content"`
|
||||
MarkdownContent *string `json:"markdown_content"`
|
||||
CoverAssetURL *string `json:"cover_asset_url"`
|
||||
}
|
||||
|
||||
func NewDesktopContentService(
|
||||
pool *pgxpool.Pool,
|
||||
auditLogs *auditlog.AsyncWriter,
|
||||
objectStorage objectstorage.Client,
|
||||
cache sharedcache.Cache,
|
||||
) *DesktopContentService {
|
||||
return &DesktopContentService{
|
||||
articles: NewArticleService(pool, auditLogs, objectStorage).WithCache(cache),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *DesktopContentService) Article(
|
||||
ctx context.Context,
|
||||
client *repository.DesktopClient,
|
||||
articleID int64,
|
||||
) (*DesktopArticleContentView, error) {
|
||||
if client == nil {
|
||||
return nil, response.ErrUnauthorized(40108, "desktop_client_missing", "desktop client context is required")
|
||||
}
|
||||
|
||||
detail, found, err := s.articles.loadArticleDetail(ctx, client.TenantID, articleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found || detail == nil {
|
||||
return nil, response.ErrNotFound(40411, "article_not_found", "article not found")
|
||||
}
|
||||
|
||||
title := ""
|
||||
if detail.Title != nil {
|
||||
title = *detail.Title
|
||||
}
|
||||
|
||||
return &DesktopArticleContentView{
|
||||
ArticleID: detail.ID,
|
||||
Title: title,
|
||||
HTMLContent: detail.HTMLContent,
|
||||
MarkdownContent: detail.MarkdownContent,
|
||||
CoverAssetURL: detail.CoverAssetURL,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user