package app import ( "context" "encoding/json" "errors" "github.com/jackc/pgx/v5" "github.com/geo-platform/tenant-api/internal/shared/auth" "github.com/geo-platform/tenant-api/internal/shared/response" "github.com/geo-platform/tenant-api/internal/tenant/domain" "github.com/geo-platform/tenant-api/internal/tenant/repository" ) type WorkspaceService struct { repo repository.WorkspaceRepository templates repository.TemplateRepository supportedPlatformCount int } func NewWorkspaceService(repo repository.WorkspaceRepository, templates repository.TemplateRepository) *WorkspaceService { return &WorkspaceService{repo: repo, templates: templates, supportedPlatformCount: 5} } func (s *WorkspaceService) Overview(ctx context.Context) (*domain.WorkspaceOverview, error) { actor := auth.MustActor(ctx) articleCount, err := s.repo.CountArticlesByTenant(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to count articles") } publishedCount, err := s.repo.CountPublishedArticlesByTenant(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to count published articles") } brandCount, err := s.repo.CountBrandsByTenant(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to count brands") } return &domain.WorkspaceOverview{ ArticleCount: articleCount, PublishedCount: publishedCount, BrandCount: brandCount, SupportedPlatformCount: s.supportedPlatformCount, }, nil } func (s *WorkspaceService) RecentArticles(ctx context.Context) ([]domain.RecentArticle, error) { actor := auth.MustActor(ctx) rows, err := s.repo.GetRecentArticles(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to fetch recent articles") } articles := make([]domain.RecentArticle, 0, len(rows)) for _, row := range rows { articles = append(articles, domain.RecentArticle{ ID: row.ID, GenerateStatus: row.GenerateStatus, PublishStatus: row.PublishStatus, SourceType: row.SourceType, CreatedAt: row.CreatedAt, Title: row.Title, WordCount: row.WordCount, SourceLabel: row.SourceLabel, TemplateName: row.TemplateName, }) } return articles, nil } func (s *WorkspaceService) QuotaSummary(ctx context.Context) (*domain.QuotaSummary, error) { actor := auth.MustActor(ctx) balance, err := s.repo.GetQuotaSummary(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to get quota balance") } plan, err := s.repo.GetActivePlanForTenant(ctx, actor.TenantID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return &domain.QuotaSummary{Balance: balance}, nil } return nil, response.ErrInternal(50010, "query_failed", "failed to get active plan") } var policy map[string]int _ = json.Unmarshal(plan.QuotaPolicyJSON, &policy) total := policy["article_generation"] return &domain.QuotaSummary{ PlanCode: plan.PlanCode, PlanName: plan.PlanName, TotalQuota: total, UsedQuota: total - balance, Balance: balance, }, nil } type TemplateCard struct { ID int64 `json:"id"` Scope string `json:"scope"` TemplateKey string `json:"template_key"` TemplateName string `json:"template_name"` OriginType string `json:"origin_type"` CardConfigJSON map[string]interface{} `json:"card_config"` PromptVisibility string `json:"prompt_visibility"` } func (s *WorkspaceService) TemplateCards(ctx context.Context) ([]TemplateCard, error) { actor := auth.MustActor(ctx) rows, err := s.templates.ListTemplates(ctx, actor.TenantID) if err != nil { return nil, response.ErrInternal(50010, "query_failed", "failed to fetch templates") } cards := make([]TemplateCard, 0, len(rows)) for _, row := range rows { card := TemplateCard{ ID: row.ID, Scope: row.Scope, TemplateKey: row.TemplateKey, TemplateName: row.TemplateName, OriginType: row.OriginType, PromptVisibility: effectivePromptVisibility(&row, actor.TenantID), } _ = json.Unmarshal(row.CardConfigJSON, &card.CardConfigJSON) cards = append(cards, card) } return cards, nil }