timer routes

This commit is contained in:
Oliver Bryan
2026-01-09 21:54:21 +00:00
parent f04baeb052
commit d6604d2843
9 changed files with 252 additions and 0 deletions

View File

@@ -24,6 +24,10 @@ import projectDelete from "./project/delete";
import projectUpdate from "./project/update";
import projectWithCreator from "./project/with-creator";
import projectsWithCreators from "./project/with-creators";
import timerEnd from "./timer/end";
import timerGet from "./timer/get";
import timerToggle from "./timer/toggle";
import timers from "./timers";
import userByUsername from "./user/by-username";
import userUpdate from "./user/update";
import userUploadAvatar from "./user/upload-avatar";
@@ -65,4 +69,9 @@ export const routes = {
projectsByOrganisation,
projectsAll,
projectsWithCreators,
timerToggle,
timerGet,
timerEnd,
timers,
};

View File

@@ -0,0 +1,39 @@
import { calculateBreakTimeMs, calculateWorkTimeMs } from "@issue/shared";
import type { AuthedRequest } from "../../auth/middleware";
import { endTimedSession, getActiveTimedSession } from "../../db/queries";
// POST /timer/end
export default async function timerEnd(req: AuthedRequest) {
const url = new URL(req.url);
const issueId = url.searchParams.get("issueId");
if (!issueId || Number.isNaN(Number(issueId))) {
return new Response("missing issue id", { status: 400 });
}
const activeSession = await getActiveTimedSession(req.userId, Number(issueId));
if (!activeSession) {
return new Response("no active timer", { status: 400 });
}
// already ended - return existing without modification
if (activeSession.endedAt) {
return Response.json({
...activeSession,
workTimeMs: calculateWorkTimeMs(activeSession.timestamps),
breakTimeMs: calculateBreakTimeMs(activeSession.timestamps),
isRunning: false,
});
}
const ended = await endTimedSession(activeSession.id, activeSession.timestamps);
if (!ended) {
return new Response("failed to end timer", { status: 500 });
}
return Response.json({
...ended,
workTimeMs: calculateWorkTimeMs(ended.timestamps),
breakTimeMs: calculateBreakTimeMs(ended.timestamps),
isRunning: false,
});
}

View File

@@ -0,0 +1,26 @@
import { calculateBreakTimeMs, calculateWorkTimeMs, isTimerRunning } from "@issue/shared";
import type { AuthedRequest } from "../../auth/middleware";
import { getActiveTimedSession } from "../../db/queries";
// GET /timer?issueId=123
export default async function timerGet(req: AuthedRequest) {
const url = new URL(req.url);
const issueId = url.searchParams.get("issueId");
if (!issueId || Number.isNaN(Number(issueId))) {
return new Response("missing issue id", { status: 400 });
}
const activeSession = await getActiveTimedSession(req.userId, Number(issueId));
if (!activeSession) {
return Response.json(null);
}
const running = isTimerRunning(activeSession.timestamps);
return Response.json({
...activeSession,
workTimeMs: calculateWorkTimeMs(activeSession.timestamps),
breakTimeMs: calculateBreakTimeMs(activeSession.timestamps),
isRunning: running,
});
}

View File

@@ -0,0 +1,40 @@
import { calculateBreakTimeMs, calculateWorkTimeMs, isTimerRunning } from "@issue/shared";
import type { AuthedRequest } from "../../auth/middleware";
import { appendTimestamp, createTimedSession, getActiveTimedSession } from "../../db/queries";
// POST /timer/toggle?issueId=123
export default async function timerToggle(req: AuthedRequest) {
const url = new URL(req.url);
const issueId = url.searchParams.get("issueId");
if (!issueId || Number.isNaN(Number(issueId))) {
return new Response("missing issue id", { status: 400 });
}
const activeSession = await getActiveTimedSession(req.userId, Number(issueId));
if (!activeSession) {
// no active session, create new one with first timestamp
const newSession = await createTimedSession(req.userId, Number(issueId));
return Response.json({
...newSession,
workTimeMs: 0,
breakTimeMs: 0,
isRunning: true,
});
}
// active session exists, append timestamp (toggle)
const updated = await appendTimestamp(activeSession.id, activeSession.timestamps);
if (!updated) {
return new Response("failed to update timer", { status: 500 });
}
const running = isTimerRunning(updated.timestamps);
return Response.json({
...updated,
workTimeMs: calculateWorkTimeMs(updated.timestamps),
breakTimeMs: calculateBreakTimeMs(updated.timestamps),
isRunning: running,
});
}

View File

@@ -0,0 +1,24 @@
import { calculateBreakTimeMs, calculateWorkTimeMs, isTimerRunning } from "@issue/shared";
import type { AuthedRequest } from "../auth/middleware";
import { getUserTimedSessions } from "../db/queries";
// GET /timers?limit=50&offset=0
export default async function timers(req: AuthedRequest) {
const url = new URL(req.url);
const limitParam = url.searchParams.get("limit");
const offsetParam = url.searchParams.get("offset");
const limit = limitParam ? Number(limitParam) : 50;
const offset = offsetParam ? Number(offsetParam) : 0;
const sessions = await getUserTimedSessions(req.userId, limit, offset);
const enriched = sessions.map((session) => ({
...session,
workTimeMs: calculateWorkTimeMs(session.timestamps),
breakTimeMs: calculateBreakTimeMs(session.timestamps),
isRunning: session.endedAt === null && isTimerRunning(session.timestamps),
}));
return Response.json(enriched);
}