Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions .claude/settings.local.json

This file was deleted.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,7 @@ Thumbs.db
.idea/
*.swp

# Claude Code local settings
.claude/

desktop.ini
54 changes: 40 additions & 14 deletions src/app/api/goals/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

type Recurrence = "none" | "weekly" | "monthly";

const VALID_RECURRENCES = ["none", "weekly", "monthly"] as const;
const MAX_TITLE_LEN = 100;
const MAX_UNIT_LEN = 30;
const MIN_TARGET = 1;
const MAX_TARGET = 10_000;

function getPeriodStart(recurrence: Recurrence): string {
const now = new Date();
if (recurrence === "weekly") {
Expand Down Expand Up @@ -106,21 +112,41 @@
return Response.json({ error: "Unauthorized" }, { status: 401 });
}

const body = (await req.json()) as {
title?: string;
target?: number;
unit?: string;
recurrence?: Recurrence;
};
let body: unknown;
try {
body = await req.json();
} catch {
return Response.json({ error: "Invalid JSON" }, { status: 400 });
}

if (!body.title || typeof body.target !== "number" || body.target <= 0) {

Check failure on line 122 in src/app/api/goals/route.ts

View workflow job for this annotation

GitHub Actions / Type check

'body' is of type 'unknown'.

Check failure on line 122 in src/app/api/goals/route.ts

View workflow job for this annotation

GitHub Actions / Type check

'body' is of type 'unknown'.

Check failure on line 122 in src/app/api/goals/route.ts

View workflow job for this annotation

GitHub Actions / Type check

'body' is of type 'unknown'.
return Response.json({ error: "title and positive target required" }, { status: 400 });
}

const recurrence: Recurrence = body.recurrence ?? "none";
if (!["none", "weekly", "monthly"].includes(recurrence)) {
return Response.json({ error: "Invalid recurrence value" }, { status: 400 });
const { title, target, unit, recurrence } = body as Record<string, unknown>;

if (typeof title !== "string" || title.trim().length === 0) {
return Response.json({ error: "title must be a non-empty string" }, { status: 400 });
}
if (title.length > MAX_TITLE_LEN) {
return Response.json({ error: `title must be ${MAX_TITLE_LEN} characters or fewer` }, { status: 400 });
}
if (
typeof target !== "number" ||
!Number.isInteger(target) ||
target < MIN_TARGET ||
target > MAX_TARGET
) {
return Response.json(
{ error: `target must be an integer between ${MIN_TARGET} and ${MAX_TARGET}` },
{ status: 400 }
);
}

const safeUnit = typeof unit === "string" ? unit.slice(0, MAX_UNIT_LEN) : "commits";
const safeRecurrence: Recurrence = VALID_RECURRENCES.includes(recurrence as Recurrence)
? (recurrence as Recurrence)
: "none";

const user = await resolveAppUser(session.githubId, session.githubLogin);
if (!user) return Response.json({ error: "User not found" }, { status: 404 });
Expand All @@ -129,11 +155,11 @@
.from("goals")
.insert({
user_id: user.id,
title: body.title,
target: body.target,
unit: body.unit ?? "commits",
recurrence,
period_start: getPeriodStart(recurrence),
title: title.trim(),
target,
unit: safeUnit,
recurrence: safeRecurrence,
period_start: getPeriodStart(safeRecurrence),
current: 0,
})
.select()
Expand Down
Loading