feat(schedule): support KOL subscription targets and random time windows
Frontend CI / Frontend (push) Successful in 3m37s
Backend CI / Backend (push) Failing after 6m47s

Extend schedule tasks to dispatch KOL subscription prompts in addition
to prompt rules, add daily/weekly scheduling with fixed or random time
windows, and track dispatch outcomes (last run, status, consecutive
failures).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-22 23:51:45 +08:00
parent 9cc6537e68
commit c28c4f1419
20 changed files with 3034 additions and 329 deletions
@@ -0,0 +1,26 @@
DROP INDEX IF EXISTS idx_schedule_task_subscription_prompt;
DROP INDEX IF EXISTS idx_schedule_task_target;
ALTER TABLE schedule_tasks
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_consecutive_failures,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_generation_input_json,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_random_window,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_schedule_time_mode,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_schedule_days_json,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_schedule_kind,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_target_ref,
DROP CONSTRAINT IF EXISTS chk_schedule_tasks_target_type;
ALTER TABLE schedule_tasks
DROP COLUMN IF EXISTS consecutive_failures,
DROP COLUMN IF EXISTS last_dispatch_error,
DROP COLUMN IF EXISTS last_dispatch_status,
DROP COLUMN IF EXISTS last_run_at,
DROP COLUMN IF EXISTS generation_input_json,
DROP COLUMN IF EXISTS random_window_end,
DROP COLUMN IF EXISTS random_window_start,
DROP COLUMN IF EXISTS schedule_time_mode,
DROP COLUMN IF EXISTS schedule_days,
DROP COLUMN IF EXISTS schedule_kind,
DROP COLUMN IF EXISTS subscription_prompt_id,
DROP COLUMN IF EXISTS target_type;
@@ -0,0 +1,109 @@
ALTER TABLE schedule_tasks
ALTER COLUMN prompt_rule_id DROP NOT NULL;
ALTER TABLE schedule_tasks
ADD COLUMN IF NOT EXISTS target_type VARCHAR(32) NOT NULL DEFAULT 'prompt_rule',
ADD COLUMN IF NOT EXISTS subscription_prompt_id BIGINT REFERENCES kol_subscription_prompts(id),
ADD COLUMN IF NOT EXISTS schedule_kind VARCHAR(20) NOT NULL DEFAULT 'daily',
ADD COLUMN IF NOT EXISTS schedule_days JSONB NOT NULL DEFAULT '["mon","tue","wed","thu","fri","sat","sun"]'::jsonb,
ADD COLUMN IF NOT EXISTS schedule_time_mode VARCHAR(20) NOT NULL DEFAULT 'fixed',
ADD COLUMN IF NOT EXISTS random_window_start TIME NOT NULL DEFAULT '01:00:00',
ADD COLUMN IF NOT EXISTS random_window_end TIME NOT NULL DEFAULT '05:00:00',
ADD COLUMN IF NOT EXISTS generation_input_json JSONB NOT NULL DEFAULT '{}'::jsonb,
ADD COLUMN IF NOT EXISTS last_run_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS last_dispatch_status VARCHAR(20),
ADD COLUMN IF NOT EXISTS last_dispatch_error TEXT,
ADD COLUMN IF NOT EXISTS consecutive_failures INT NOT NULL DEFAULT 0;
UPDATE schedule_tasks
SET target_type = 'prompt_rule'
WHERE target_type IS NULL OR btrim(target_type) = '';
UPDATE schedule_tasks
SET schedule_kind = 'daily',
schedule_days = '["mon","tue","wed","thu","fri","sat","sun"]'::jsonb
WHERE schedule_days IS NULL
OR jsonb_typeof(schedule_days) <> 'array'
OR jsonb_array_length(schedule_days) = 0;
UPDATE schedule_tasks
SET schedule_time_mode = 'fixed'
WHERE schedule_time_mode IS NULL OR btrim(schedule_time_mode) = '';
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_target_type'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_target_type
CHECK (target_type IN ('prompt_rule', 'kol_subscription_prompt'));
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_target_ref'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_target_ref
CHECK (
(target_type = 'prompt_rule' AND prompt_rule_id IS NOT NULL)
OR (target_type = 'kol_subscription_prompt' AND subscription_prompt_id IS NOT NULL)
);
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_schedule_kind'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_schedule_kind
CHECK (schedule_kind IN ('daily', 'weekly'));
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_schedule_days_json'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_schedule_days_json
CHECK (jsonb_typeof(schedule_days) = 'array' AND jsonb_array_length(schedule_days) BETWEEN 1 AND 7);
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_schedule_time_mode'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_schedule_time_mode
CHECK (schedule_time_mode IN ('fixed', 'random_window'));
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_random_window'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_random_window
CHECK (random_window_end > random_window_start);
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_generation_input_json'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_generation_input_json
CHECK (jsonb_typeof(generation_input_json) = 'object');
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint WHERE conname = 'chk_schedule_tasks_consecutive_failures'
) THEN
ALTER TABLE schedule_tasks
ADD CONSTRAINT chk_schedule_tasks_consecutive_failures
CHECK (consecutive_failures >= 0);
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_schedule_task_target
ON schedule_tasks(tenant_id, target_type, created_at DESC)
WHERE deleted_at IS NULL;
CREATE INDEX IF NOT EXISTS idx_schedule_task_subscription_prompt
ON schedule_tasks(tenant_id, subscription_prompt_id, created_at DESC)
WHERE deleted_at IS NULL AND subscription_prompt_id IS NOT NULL;