feat(enterprise-site): add WordPress support and scheduled auto-publish to sites
Frontend CI / Frontend (push) Successful in 4m17s
Backend CI / Backend (push) Failing after 6m42s

Add WordPress as an enterprise-site CMS type alongside pbootcms, and let
schedule tasks auto-publish generated articles to enterprise sites.

- WordPress connection/publisher service and tests; register wordpress
  media platform and relax cms_type CHECK constraint
- New publish_enterprise_site_targets JSONB column on schedule_tasks with
  array type constraint; thread targets through scheduler dispatch,
  prompt/KOL generation, and worker
- Admin UI: enterprise-site targets in generate/schedule/publish flows,
  KOL package form, MediaView, and i18n strings

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 19:44:24 +08:00
parent 41f5623791
commit c7bad83496
30 changed files with 2946 additions and 696 deletions
@@ -40,30 +40,31 @@ type ScheduleDispatchWorker struct {
}
type dueScheduleTask struct {
ID int64
TenantID int64
WorkspaceID int64
OperatorID *int64
TargetType string
PromptRuleID *int64
SubscriptionPromptID *int64
BrandID *int64
Name string
CronExpr string
ScheduleDays []string
ScheduleTimeMode string
RandomWindowStart string
RandomWindowEnd string
GenerationInput map[string]interface{}
AutoPublish bool
PublishAccountIDs []string
CoverAssetURL *string
CoverImageAssetID *int64
EnableWebSearch bool
GenerateCount int
StartAt *time.Time
EndAt *time.Time
ScheduledFor time.Time
ID int64
TenantID int64
WorkspaceID int64
OperatorID *int64
TargetType string
PromptRuleID *int64
SubscriptionPromptID *int64
BrandID *int64
Name string
CronExpr string
ScheduleDays []string
ScheduleTimeMode string
RandomWindowStart string
RandomWindowEnd string
GenerationInput map[string]interface{}
AutoPublish bool
PublishAccountIDs []string
PublishEnterpriseSiteTargets []tenantapp.ScheduleEnterpriseSiteTarget
CoverAssetURL *string
CoverImageAssetID *int64
EnableWebSearch bool
GenerateCount int
StartAt *time.Time
EndAt *time.Time
ScheduledFor time.Time
}
type scheduleTaskCacheUpdate struct {
@@ -376,7 +377,7 @@ func (w *ScheduleDispatchWorker) claimDueSchedules(ctx context.Context, runtimeC
SELECT id, tenant_id, workspace_id, operator_id, target_type, prompt_rule_id,
subscription_prompt_id, brand_id, name, cron_expr, schedule_days, schedule_time_mode,
random_window_start::text, random_window_end::text, generation_input_json,
auto_publish, publish_account_ids, cover_asset_url, cover_image_asset_id,
auto_publish, publish_account_ids, publish_enterprise_site_targets, cover_asset_url, cover_image_asset_id,
enable_web_search, generate_count, start_at, end_at, next_run_at
FROM schedule_tasks
WHERE deleted_at IS NULL
@@ -400,17 +401,18 @@ func (w *ScheduleDispatchWorker) claimDueSchedules(ctx context.Context, runtimeC
}, 0, runtimeCfg.BatchSize)
for rows.Next() {
var (
task dueScheduleTask
workspaceID pgtype.Int8
operatorID pgtype.Int8
promptRuleID pgtype.Int8
subscriptionPromptID pgtype.Int8
generationInputJSON []byte
scheduleDaysJSON []byte
publishAccountIDsJSON []byte
startAt pgtype.Timestamptz
endAt pgtype.Timestamptz
nextRunAt pgtype.Timestamptz
task dueScheduleTask
workspaceID pgtype.Int8
operatorID pgtype.Int8
promptRuleID pgtype.Int8
subscriptionPromptID pgtype.Int8
generationInputJSON []byte
scheduleDaysJSON []byte
publishAccountIDsJSON []byte
publishEnterpriseSiteTargetsJSON []byte
startAt pgtype.Timestamptz
endAt pgtype.Timestamptz
nextRunAt pgtype.Timestamptz
)
if err := rows.Scan(
&task.ID,
@@ -430,6 +432,7 @@ func (w *ScheduleDispatchWorker) claimDueSchedules(ctx context.Context, runtimeC
&generationInputJSON,
&task.AutoPublish,
&publishAccountIDsJSON,
&publishEnterpriseSiteTargetsJSON,
&task.CoverAssetURL,
&task.CoverImageAssetID,
&task.EnableWebSearch,
@@ -456,6 +459,7 @@ func (w *ScheduleDispatchWorker) claimDueSchedules(ctx context.Context, runtimeC
task.TargetType = tenantapp.ScheduleTargetPromptRule
}
task.PublishAccountIDs = decodeDueSchedulePublishAccountIDs(publishAccountIDsJSON)
task.PublishEnterpriseSiteTargets = tenantapp.DecodeScheduleEnterpriseSiteTargets(publishEnterpriseSiteTargetsJSON)
if task.GenerateCount <= 0 {
task.GenerateCount = 1
}
@@ -589,20 +593,21 @@ func (w *ScheduleDispatchWorker) enqueueDueGeneration(ctx context.Context, task
return nil, errors.New("schedule prompt_rule target missing prompt_rule_id")
}
return w.promptRuleService.EnqueueScheduledGeneration(ctx, tenantapp.SchedulePromptRuleGenerationInput{
ScheduleTaskID: task.ID,
OperatorID: task.OperatorID,
TenantID: task.TenantID,
PromptRuleID: *task.PromptRuleID,
BrandID: task.BrandID,
Name: task.Name,
WorkspaceID: task.WorkspaceID,
AutoPublish: task.AutoPublish,
PublishAccountIDs: task.PublishAccountIDs,
CoverAssetURL: task.CoverAssetURL,
CoverImageAssetID: task.CoverImageAssetID,
EnableWebSearch: task.EnableWebSearch,
GenerateCount: generateCount,
ScheduledFor: task.ScheduledFor,
ScheduleTaskID: task.ID,
OperatorID: task.OperatorID,
TenantID: task.TenantID,
PromptRuleID: *task.PromptRuleID,
BrandID: task.BrandID,
Name: task.Name,
WorkspaceID: task.WorkspaceID,
AutoPublish: task.AutoPublish,
PublishAccountIDs: task.PublishAccountIDs,
PublishEnterpriseSiteTargets: task.PublishEnterpriseSiteTargets,
CoverAssetURL: task.CoverAssetURL,
CoverImageAssetID: task.CoverImageAssetID,
EnableWebSearch: task.EnableWebSearch,
GenerateCount: generateCount,
ScheduledFor: task.ScheduledFor,
})
case tenantapp.ScheduleTargetKolSubscriptionPrompt:
if w.kolService == nil {
@@ -613,22 +618,23 @@ func (w *ScheduleDispatchWorker) enqueueDueGeneration(ctx context.Context, task
}
variables, _ := task.GenerationInput["variables"].(map[string]interface{})
kolResp, err := w.kolService.EnqueueScheduledGeneration(ctx, tenantapp.ScheduleKolGenerationInput{
ScheduleTaskID: task.ID,
OperatorID: task.OperatorID,
TenantID: task.TenantID,
WorkspaceID: task.WorkspaceID,
BrandID: task.BrandID,
SubscriptionPromptID: *task.SubscriptionPromptID,
Name: task.Name,
Variables: variables,
EnableWebSearch: scheduleGenerationInputBool(task.GenerationInput, "enable_web_search", task.EnableWebSearch),
KnowledgeGroupIDs: scheduleGenerationInputInt64s(task.GenerationInput, "knowledge_group_ids"),
AutoPublish: task.AutoPublish,
PublishAccountIDs: task.PublishAccountIDs,
CoverAssetURL: task.CoverAssetURL,
CoverImageAssetID: task.CoverImageAssetID,
GenerateCount: generateCount,
ScheduledFor: task.ScheduledFor,
ScheduleTaskID: task.ID,
OperatorID: task.OperatorID,
TenantID: task.TenantID,
WorkspaceID: task.WorkspaceID,
BrandID: task.BrandID,
SubscriptionPromptID: *task.SubscriptionPromptID,
Name: task.Name,
Variables: variables,
EnableWebSearch: scheduleGenerationInputBool(task.GenerationInput, "enable_web_search", task.EnableWebSearch),
KnowledgeGroupIDs: scheduleGenerationInputInt64s(task.GenerationInput, "knowledge_group_ids"),
AutoPublish: task.AutoPublish,
PublishAccountIDs: task.PublishAccountIDs,
PublishEnterpriseSiteTargets: task.PublishEnterpriseSiteTargets,
CoverAssetURL: task.CoverAssetURL,
CoverImageAssetID: task.CoverImageAssetID,
GenerateCount: generateCount,
ScheduledFor: task.ScheduledFor,
})
if err != nil {
return nil, err