feat: scope articles by brand

This commit is contained in:
2026-05-20 15:37:25 +08:00
parent 5fb9d0b0dd
commit dd082e2ed1
72 changed files with 3213 additions and 432 deletions
@@ -101,15 +101,23 @@ type ScheduleTaskStatusRequest struct {
func (s *ScheduleTaskService) List(ctx context.Context, params ScheduleTaskListParams) (*ScheduleTaskListResponse, error) {
actor := auth.MustActor(ctx)
return sharedcache.LoadJSON(ctx, s.cache, &s.cacheGroup, scheduleTaskListCacheKey(actor.TenantID, params), defaultCacheTTL(), func(loadCtx context.Context) (*ScheduleTaskListResponse, error) {
return s.loadScheduleTasks(loadCtx, actor.TenantID, params)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return nil, err
}
return sharedcache.LoadJSON(ctx, s.cache, &s.cacheGroup, scheduleTaskListCacheKey(actor.TenantID, brandID, params), defaultCacheTTL(), func(loadCtx context.Context) (*ScheduleTaskListResponse, error) {
return s.loadScheduleTasks(loadCtx, actor.TenantID, brandID, params)
})
}
func (s *ScheduleTaskService) Detail(ctx context.Context, id int64) (*ScheduleTaskResponse, error) {
actor := auth.MustActor(ctx)
record, found, err := sharedcache.LoadJSONWithEmpty(ctx, s.cache, &s.cacheGroup, scheduleTaskDetailCacheKey(actor.TenantID, id), defaultCacheTTL(), defaultCacheEmptyTTL(), func(loadCtx context.Context) (*ScheduleTaskResponse, bool, error) {
return s.loadScheduleTaskDetail(loadCtx, actor.TenantID, id)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return nil, err
}
record, found, err := sharedcache.LoadJSONWithEmpty(ctx, s.cache, &s.cacheGroup, scheduleTaskDetailCacheKey(actor.TenantID, brandID, id), defaultCacheTTL(), defaultCacheEmptyTTL(), func(loadCtx context.Context) (*ScheduleTaskResponse, bool, error) {
return s.loadScheduleTaskDetail(loadCtx, actor.TenantID, brandID, id)
})
if err != nil {
return nil, err
@@ -122,6 +130,11 @@ func (s *ScheduleTaskService) Detail(ctx context.Context, id int64) (*ScheduleTa
func (s *ScheduleTaskService) Create(ctx context.Context, req ScheduleTaskRequest) (*ScheduleTaskResponse, error) {
actor := auth.MustActor(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return nil, err
}
req.BrandID = &brandID
if err := validateScheduleCronExpr(req.CronExpr); err != nil {
return nil, err
@@ -220,6 +233,12 @@ func (s *ScheduleTaskService) Create(ctx context.Context, req ScheduleTaskReques
func (s *ScheduleTaskService) Update(ctx context.Context, id int64, req ScheduleTaskRequest) error {
actor := auth.MustActor(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return err
}
req.BrandID = &brandID
if err := validateScheduleCronExpr(req.CronExpr); err != nil {
return err
}
@@ -247,13 +266,13 @@ func (s *ScheduleTaskService) Update(ctx context.Context, id int64, req Schedule
var endAt pgtype.Timestamptz
err = tx.QueryRow(ctx, `
UPDATE schedule_tasks SET prompt_rule_id = $1, brand_id = $2, name = $3,
cron_expr = $4, enable_web_search = $5, generate_count = $6,
workspace_id = $7, auto_publish = $8, publish_account_ids = $9::jsonb,
cover_asset_url = $10, cover_image_asset_id = $11,
start_at = $12::timestamptz, end_at = $13::timestamptz, operator_id = $14, updated_at = NOW()
WHERE id = $15 AND tenant_id = $16 AND deleted_at IS NULL
cron_expr = $4, enable_web_search = $5, generate_count = $6,
workspace_id = $7, auto_publish = $8, publish_account_ids = $9::jsonb,
cover_asset_url = $10, cover_image_asset_id = $11,
start_at = $12::timestamptz, end_at = $13::timestamptz, operator_id = $14, updated_at = NOW()
WHERE id = $15 AND tenant_id = $16 AND brand_id = $17 AND deleted_at IS NULL
RETURNING status, start_at, end_at
`, req.PromptRuleID, req.BrandID, req.Name, req.CronExpr, enableWebSearch, generateCount, actor.PrimaryWorkspaceID, publishConfig.AutoPublish, publishConfig.AccountIDsJSON, publishConfig.CoverAssetURL, publishConfig.CoverImageAssetID, req.StartAt, req.EndAt, actor.UserID, id, actor.TenantID).
`, req.PromptRuleID, req.BrandID, req.Name, req.CronExpr, enableWebSearch, generateCount, actor.PrimaryWorkspaceID, publishConfig.AutoPublish, publishConfig.AccountIDsJSON, publishConfig.CoverAssetURL, publishConfig.CoverImageAssetID, req.StartAt, req.EndAt, actor.UserID, id, actor.TenantID, brandID).
Scan(&status, &startAt, &endAt)
if err != nil {
return response.ErrNotFound(40432, "schedule_not_found", "schedule task not found")
@@ -282,10 +301,14 @@ func (s *ScheduleTaskService) Update(ctx context.Context, id int64, req Schedule
func (s *ScheduleTaskService) Delete(ctx context.Context, id int64) error {
actor := auth.MustActor(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return err
}
tag, err := s.pool.Exec(ctx, `
UPDATE schedule_tasks SET deleted_at = NOW(), updated_at = NOW()
WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL
`, id, actor.TenantID)
WHERE id = $1 AND tenant_id = $2 AND brand_id = $3 AND deleted_at IS NULL
`, id, actor.TenantID, brandID)
if err != nil || tag.RowsAffected() == 0 {
return response.ErrNotFound(40432, "schedule_not_found", "schedule task not found")
}
@@ -311,6 +334,10 @@ func (s *ScheduleTaskService) Delete(ctx context.Context, id int64) error {
func (s *ScheduleTaskService) ToggleStatus(ctx context.Context, id int64, req ScheduleTaskStatusRequest) error {
actor := auth.MustActor(ctx)
brandID, err := requireCurrentBrandID(ctx)
if err != nil {
return err
}
tx, err := s.pool.Begin(ctx)
if err != nil {
return response.ErrInternal(50010, "update_failed", "failed to begin schedule task transaction")
@@ -323,9 +350,9 @@ func (s *ScheduleTaskService) ToggleStatus(ctx context.Context, id int64, req Sc
var endAt pgtype.Timestamptz
err = tx.QueryRow(ctx, `
UPDATE schedule_tasks SET status = $1, operator_id = $2, updated_at = NOW()
WHERE id = $3 AND tenant_id = $4 AND deleted_at IS NULL
WHERE id = $3 AND tenant_id = $4 AND brand_id = $5 AND deleted_at IS NULL
RETURNING cron_expr, status, start_at, end_at
`, req.Status, actor.UserID, id, actor.TenantID).
`, req.Status, actor.UserID, id, actor.TenantID, brandID).
Scan(&cronExpr, &status, &startAt, &endAt)
if err != nil {
return response.ErrNotFound(40432, "schedule_not_found", "schedule task not found")
@@ -352,7 +379,7 @@ func (s *ScheduleTaskService) ToggleStatus(ctx context.Context, id int64, req Sc
return nil
}
func (s *ScheduleTaskService) loadScheduleTasks(ctx context.Context, tenantID int64, params ScheduleTaskListParams) (*ScheduleTaskListResponse, error) {
func (s *ScheduleTaskService) loadScheduleTasks(ctx context.Context, tenantID, brandID int64, params ScheduleTaskListParams) (*ScheduleTaskListResponse, error) {
if params.Page < 1 {
params.Page = 1
}
@@ -364,13 +391,13 @@ func (s *ScheduleTaskService) loadScheduleTasks(ctx context.Context, tenantID in
var total int64
if err := s.pool.QueryRow(ctx, `
SELECT COUNT(*) FROM schedule_tasks
WHERE tenant_id = $1 AND deleted_at IS NULL
AND ($2::bigint IS NULL OR prompt_rule_id = $2)
AND ($3::text IS NULL OR status = $3)
AND ($4::text IS NULL OR name ILIKE '%' || $4 || '%')
AND ($5::timestamptz IS NULL OR created_at >= $5)
AND ($6::timestamptz IS NULL OR created_at <= $6)
`, tenantID, params.PromptRuleID, params.Status, params.Keyword, params.CreatedFrom, params.CreatedTo).Scan(&total); err != nil {
WHERE tenant_id = $1 AND brand_id = $2 AND deleted_at IS NULL
AND ($3::bigint IS NULL OR prompt_rule_id = $3)
AND ($4::text IS NULL OR status = $4)
AND ($5::text IS NULL OR name ILIKE '%' || $5 || '%')
AND ($6::timestamptz IS NULL OR created_at >= $6)
AND ($7::timestamptz IS NULL OR created_at <= $7)
`, tenantID, brandID, params.PromptRuleID, params.Status, params.Keyword, params.CreatedFrom, params.CreatedTo).Scan(&total); err != nil {
return nil, response.ErrInternal(50010, "count_failed", "failed to count schedule tasks")
}
@@ -384,15 +411,15 @@ func (s *ScheduleTaskService) loadScheduleTasks(ctx context.Context, tenantID in
FROM schedule_tasks st
LEFT JOIN prompt_rules pr ON pr.id = st.prompt_rule_id
LEFT JOIN brands b ON b.id = st.brand_id
WHERE st.tenant_id = $1 AND st.deleted_at IS NULL
AND ($2::bigint IS NULL OR st.prompt_rule_id = $2)
AND ($3::text IS NULL OR st.status = $3)
AND ($4::text IS NULL OR st.name ILIKE '%' || $4 || '%')
AND ($5::timestamptz IS NULL OR st.created_at >= $5)
AND ($6::timestamptz IS NULL OR st.created_at <= $6)
WHERE st.tenant_id = $1 AND st.brand_id = $2 AND st.deleted_at IS NULL
AND ($3::bigint IS NULL OR st.prompt_rule_id = $3)
AND ($4::text IS NULL OR st.status = $4)
AND ($5::text IS NULL OR st.name ILIKE '%' || $5 || '%')
AND ($6::timestamptz IS NULL OR st.created_at >= $6)
AND ($7::timestamptz IS NULL OR st.created_at <= $7)
ORDER BY st.created_at DESC
LIMIT $7 OFFSET $8
`, tenantID, params.PromptRuleID, params.Status, params.Keyword, params.CreatedFrom, params.CreatedTo, params.PageSize, offset)
LIMIT $8 OFFSET $9
`, tenantID, brandID, params.PromptRuleID, params.Status, params.Keyword, params.CreatedFrom, params.CreatedTo, params.PageSize, offset)
if err != nil {
return nil, response.ErrInternal(50010, "query_failed", "failed to list schedule tasks")
}
@@ -415,7 +442,7 @@ func (s *ScheduleTaskService) loadScheduleTasks(ctx context.Context, tenantID in
return &ScheduleTaskListResponse{Items: items, Total: total}, nil
}
func (s *ScheduleTaskService) loadScheduleTaskDetail(ctx context.Context, tenantID, taskID int64) (*ScheduleTaskResponse, bool, error) {
func (s *ScheduleTaskService) loadScheduleTaskDetail(ctx context.Context, tenantID, brandID, taskID int64) (*ScheduleTaskResponse, bool, error) {
row := s.pool.QueryRow(ctx, `
SELECT st.id, st.workspace_id, st.prompt_rule_id, st.brand_id, st.name,
st.cron_expr, st.auto_publish, st.publish_account_ids, st.cover_asset_url,
@@ -426,8 +453,8 @@ func (s *ScheduleTaskService) loadScheduleTaskDetail(ctx context.Context, tenant
FROM schedule_tasks st
LEFT JOIN prompt_rules pr ON pr.id = st.prompt_rule_id
LEFT JOIN brands b ON b.id = st.brand_id
WHERE st.id = $1 AND st.tenant_id = $2 AND st.deleted_at IS NULL
`, taskID, tenantID)
WHERE st.id = $1 AND st.tenant_id = $2 AND st.brand_id = $3 AND st.deleted_at IS NULL
`, taskID, tenantID, brandID)
item, err := scanScheduleTaskResponse(row)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {