Skip to content

Commit 24df77e

Browse files
ralyodioclaude
andcommitted
feat: Daily schedule option + CLI sweep command
Adds 'daily' as a valid project.schedule alongside off/weekly/monthly, threaded through the schedule toggle, new-project form, createProject / updateSchedule actions, and the scheduled-audits cron sweep (daily = 1-day next_run_at). New 'crawlproof sweep' CLI command POSTs to /api/cron/scheduled-audits with the cron secret so Daily schedules can be tested without waiting for the hourly pg_cron tick. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c504db6 commit 24df77e

7 files changed

Lines changed: 66 additions & 19 deletions

File tree

‎app/(app)/projects/new/form.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function NewProjectForm() {
2020
const [name, setName] = useState("");
2121
const [nameTouched, setNameTouched] = useState(false);
2222
const [url, setUrl] = useState("");
23-
const [schedule, setSchedule] = useState<"off" | "weekly" | "monthly">("off");
23+
const [schedule, setSchedule] = useState<"off" | "daily" | "weekly" | "monthly">("off");
2424
const [error, setError] = useState<string | null>(null);
2525

2626
function onUrlChange(next: string) {
@@ -86,6 +86,7 @@ export function NewProjectForm() {
8686
onChange={(e) => setSchedule(e.target.value as typeof schedule)}
8787
>
8888
<option value="off">No schedule</option>
89+
<option value="daily">Daily re-audits</option>
8990
<option value="weekly">Weekly re-audits</option>
9091
<option value="monthly">Monthly re-audits</option>
9192
</select>

‎app/actions/createProject.ts‎

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,20 @@ import { revalidatePath } from "next/cache";
44
import { createClient } from "@/lib/supabase/server";
55
import { isAllowedTargetUrl } from "@/lib/rateLimit";
66

7+
const DAY_MS = 24 * 60 * 60 * 1000;
8+
9+
function nextRunForSchedule(
10+
schedule: "off" | "daily" | "weekly" | "monthly",
11+
): string | null {
12+
if (schedule === "off") return null;
13+
const days = schedule === "daily" ? 1 : schedule === "weekly" ? 7 : 30;
14+
return new Date(Date.now() + days * DAY_MS).toISOString();
15+
}
16+
717
export async function createProject(input: {
818
name: string;
919
url: string;
10-
schedule: "off" | "weekly" | "monthly";
20+
schedule: "off" | "daily" | "weekly" | "monthly";
1121
}): Promise<{ ok: true; id: string } | { ok: false; error: string }> {
1222
const supabase = await createClient();
1323
const {
@@ -18,11 +28,7 @@ export async function createProject(input: {
1828
const check = isAllowedTargetUrl(input.url);
1929
if (!check.ok) return { ok: false, error: check.reason };
2030

21-
const nextRunAt = input.schedule === "off"
22-
? null
23-
: input.schedule === "weekly"
24-
? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
25-
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
31+
const nextRunAt = nextRunForSchedule(input.schedule);
2632

2733
const { data, error } = await supabase
2834
.from("projects")
@@ -43,14 +49,10 @@ export async function createProject(input: {
4349

4450
export async function updateSchedule(input: {
4551
projectId: string;
46-
schedule: "off" | "weekly" | "monthly";
52+
schedule: "off" | "daily" | "weekly" | "monthly";
4753
}): Promise<{ ok: boolean }> {
4854
const supabase = await createClient();
49-
const nextRunAt = input.schedule === "off"
50-
? null
51-
: input.schedule === "weekly"
52-
? new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
53-
: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
55+
const nextRunAt = nextRunForSchedule(input.schedule);
5456
const { error } = await supabase
5557
.from("projects")
5658
.update({ schedule: input.schedule, next_run_at: nextRunAt })

‎app/api/cron/scheduled-audits/route.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export async function POST(req: Request) {
4444
const engines: Engine[] = dedupeEngines(
4545
((p.engines as Engine[] | null) ?? ["rule"]).filter((e) => engineAvailable(e)),
4646
);
47-
const nextRun = new Date(
48-
Date.now() + (p.schedule === "weekly" ? 7 : 30) * 24 * 60 * 60 * 1000,
49-
).toISOString();
47+
const days =
48+
p.schedule === "daily" ? 1 : p.schedule === "weekly" ? 7 : 30;
49+
const nextRun = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString();
5050

5151
if (engines.length === 0) {
5252
skipped_no_engines++;

‎cli/index.ts‎

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Available commands (more land as the API stabilizes):
55
// crawlproof audit <url> [--engine=rule|claude] [--format=markdown|json]
66
// crawlproof report <token>
7+
// crawlproof sweep
78
// crawlproof help
89
//
910
// Currently `audit` runs the rule-based engine locally with no DB/credit
@@ -103,6 +104,29 @@ async function cmdReport(args: Args): Promise<number> {
103104
return 0;
104105
}
105106

107+
async function cmdSweep(_args: Args): Promise<number> {
108+
const secret = process.env.CRON_SECRET;
109+
if (!secret) {
110+
console.error("CRON_SECRET is not set — set it in .env or env.");
111+
return 2;
112+
}
113+
const base = process.env.CRAWLPROOF_SITE_URL ?? "https://crawlproof.com";
114+
const url = `${base.replace(/\/$/, "")}/api/cron/scheduled-audits`;
115+
console.error(`[cli] forcing scheduled-audits sweep at ${url} …`);
116+
const res = await fetch(url, {
117+
method: "POST",
118+
headers: { "x-cron-secret": secret, "content-type": "application/json" },
119+
body: "{}",
120+
});
121+
const body = await res.text();
122+
if (!res.ok) {
123+
console.error(`sweep failed: ${res.status} ${res.statusText}\n${body}`);
124+
return 1;
125+
}
126+
process.stdout.write(body.endsWith("\n") ? body : body + "\n");
127+
return 0;
128+
}
129+
106130
function help() {
107131
console.log(`crawlproof — AEO audit CLI (stub)
108132
@@ -120,17 +144,24 @@ COMMANDS
120144
Fetch a public report by share-token from production.
121145
Override with CRAWLPROOF_SITE_URL.
122146
147+
sweep
148+
Force the scheduled-audits cron sweep to run now. Useful for testing
149+
Daily/Weekly schedules without waiting for the hourly pg_cron tick.
150+
Requires CRON_SECRET. Override host with CRAWLPROOF_SITE_URL.
151+
123152
help
124153
Print this message.
125154
126155
ENV
127156
ANTHROPIC_API_KEY Required for --engine=claude.
128-
CRAWLPROOF_SITE_URL Override the API base URL for 'report'.
157+
CRAWLPROOF_SITE_URL Override the API base URL for 'report' and 'sweep'.
158+
CRON_SECRET Required for 'sweep'.
129159
130160
EXAMPLES
131161
crawlproof audit https://crawlproof.com
132162
crawlproof audit https://example.com --engine=claude --format=json > report.json
133163
crawlproof report r-ceiZSv2VnqypqUfjLwtrV0
164+
CRAWLPROOF_SITE_URL=http://localhost:3000 crawlproof sweep
134165
`);
135166
}
136167

@@ -142,6 +173,8 @@ async function main() {
142173
return await cmdAudit(args);
143174
case "report":
144175
return await cmdReport(args);
176+
case "sweep":
177+
return await cmdSweep(args);
145178
case "help":
146179
case "--help":
147180
case "-h":

‎components/project-shell.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type ProjectLite = {
1111
id: string;
1212
name: string;
1313
url: string;
14-
schedule: "off" | "weekly" | "monthly";
14+
schedule: "off" | "daily" | "weekly" | "monthly";
1515
status: ProjectStatus | null;
1616
engines: Engine[] | null;
1717
};

‎components/schedule-toggle.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function ScheduleToggle({
88
current,
99
}: {
1010
projectId: string;
11-
current: "off" | "weekly" | "monthly";
11+
current: "off" | "daily" | "weekly" | "monthly";
1212
}) {
1313
const [value, setValue] = useState(current);
1414
const [pending, start] = useTransition();
@@ -29,6 +29,7 @@ export function ScheduleToggle({
2929
}}
3030
>
3131
<option value="off">Off</option>
32+
<option value="daily">Daily</option>
3233
<option value="weekly">Weekly</option>
3334
<option value="monthly">Monthly</option>
3435
</select>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
-- Allow projects.schedule = 'daily' in addition to off/weekly/monthly.
2+
-- The check constraint was defined inline in 0001_init.sql; Postgres
3+
-- assigns it the default name projects_schedule_check.
4+
5+
alter table public.projects
6+
drop constraint if exists projects_schedule_check;
7+
8+
alter table public.projects
9+
add constraint projects_schedule_check
10+
check (schedule in ('off', 'daily', 'weekly', 'monthly'));

0 commit comments

Comments
 (0)