initial project setup
Fastify + Prisma backend, React + Vite frontend, Docker deployment. Multi-board feedback platform with anonymous cookie auth, passkey upgrade path, ALTCHA spam protection, plugin system, and full privacy-first architecture.
This commit is contained in:
74
packages/api/src/lib/budget.ts
Normal file
74
packages/api/src/lib/budget.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export function getCurrentPeriod(resetSchedule: string): string {
|
||||
const now = new Date();
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
|
||||
switch (resetSchedule) {
|
||||
case "weekly": {
|
||||
const startOfYear = new Date(year, 0, 1);
|
||||
const days = Math.floor((now.getTime() - startOfYear.getTime()) / 86400000);
|
||||
const week = Math.ceil((days + startOfYear.getDay() + 1) / 7);
|
||||
return `${year}-W${String(week).padStart(2, "0")}`;
|
||||
}
|
||||
case "quarterly": {
|
||||
const q = Math.ceil((now.getMonth() + 1) / 3);
|
||||
return `${year}-Q${q}`;
|
||||
}
|
||||
case "yearly":
|
||||
return `${year}`;
|
||||
case "never":
|
||||
return "lifetime";
|
||||
case "monthly":
|
||||
default:
|
||||
return `${year}-${month}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRemainingBudget(userId: string, boardId: string): Promise<number> {
|
||||
const board = await prisma.board.findUnique({ where: { id: boardId } });
|
||||
if (!board) return 0;
|
||||
|
||||
if (board.voteBudgetReset === "never" && board.voteBudget === 0) {
|
||||
return Infinity;
|
||||
}
|
||||
|
||||
const period = getCurrentPeriod(board.voteBudgetReset);
|
||||
|
||||
const used = await prisma.vote.aggregate({
|
||||
where: { voterId: userId, post: { boardId }, budgetPeriod: period },
|
||||
_sum: { weight: true },
|
||||
});
|
||||
|
||||
const spent = used._sum.weight ?? 0;
|
||||
return Math.max(0, board.voteBudget - spent);
|
||||
}
|
||||
|
||||
export function getNextResetDate(resetSchedule: string): Date {
|
||||
const now = new Date();
|
||||
|
||||
switch (resetSchedule) {
|
||||
case "weekly": {
|
||||
const d = new Date(now);
|
||||
d.setDate(d.getDate() + (7 - d.getDay()));
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}
|
||||
case "quarterly": {
|
||||
const q = Math.ceil((now.getMonth() + 1) / 3);
|
||||
return new Date(now.getFullYear(), q * 3, 1);
|
||||
}
|
||||
case "yearly":
|
||||
return new Date(now.getFullYear() + 1, 0, 1);
|
||||
case "never":
|
||||
return new Date(8640000000000000); // max date
|
||||
case "monthly":
|
||||
default: {
|
||||
const d = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user