Skip to content
Closed
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
29 changes: 26 additions & 3 deletions src/app/api/streak/freeze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,22 @@ export async function POST() {

const today = todayStr();

// Prevent users from stockpiling unused freezes
const { count } = await supabaseAdmin
.from("streak_freezes")
.select("*", { count: "exact", head: true })
.eq("user_id", user.id)
.gte("freeze_date", today);

const MAX_PENDING_FREEZES = 1;

if (count !== null && count >= MAX_PENDING_FREEZES) {
return Response.json(
{ error: "You already have a pending freeze." },
{ status: 409 }
);
}

const { data: existing } = await supabaseAdmin
.from("streak_freezes")
.select("id")
Expand All @@ -79,7 +95,10 @@ export async function POST() {

const { data: freeze, error } = await supabaseAdmin
.from("streak_freezes")
.upsert({ user_id: user.id, freeze_date: today }, { onConflict: "user_id,freeze_date" })
.upsert(
{ user_id: user.id, freeze_date: today },
{ onConflict: "user_id,freeze_date" }
)
.select()
.single();

Expand All @@ -89,7 +108,10 @@ export async function POST() {

const alreadyExisted = existing !== null;

return Response.json({ freeze, already_existed: alreadyExisted }, { status: 201 });
return Response.json(
{ freeze, already_existed: alreadyExisted },
{ status: 201 }
);
}

// DELETE /api/streak/freeze
Expand All @@ -109,7 +131,8 @@ export async function DELETE() {
.eq("user_id", user.id)
.eq("freeze_date", todayStr());

if (error) return Response.json({ error: "Failed to cancel freeze" }, { status: 500 });
if (error)
return Response.json({ error: "Failed to cancel freeze" }, { status: 500 });

return Response.json({ success: true });
}
11 changes: 6 additions & 5 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,7 @@ function checkMemoryLimit(
const active = (memoryBuckets.get(key) ?? []).filter(
(timestamp) => timestamp > cutoff
);
const reset = Math.ceil(
((active[0] ?? now) + WINDOW_SECONDS * 1000) / 1000
);
const reset = Math.ceil(((active[0] ?? now) + WINDOW_SECONDS * 1000) / 1000);

if (active.length >= limit) {
memoryBuckets.set(key, active);
Expand Down Expand Up @@ -172,7 +170,7 @@ async function checkUpstashLimit(
}

const data = await response.json();

// Upstash REST eval response format: { result: [allowed_flag, current_count] }
const [allowedFlag, currentCount] = data.result as [number, number];
const isAllowed = allowedFlag === 1;
Expand All @@ -184,7 +182,10 @@ async function checkUpstashLimit(
reset,
};
} catch (error) {
console.error("Rate-limiter cloud pipeline failure, falling back to local memory storage:", error);
console.error(
"Rate-limiter cloud pipeline failure, falling back to local memory storage:",
error
);
return null;
}
}
Expand Down
Loading