mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
added member-time-tracking route
This commit is contained in:
@@ -68,6 +68,9 @@ const main = async () => {
|
||||
"/organisation/upload-icon": withGlobalAuthed(withAuth(withCSRF(routes.organisationUploadIcon))),
|
||||
"/organisation/add-member": withGlobalAuthed(withAuth(withCSRF(routes.organisationAddMember))),
|
||||
"/organisation/members": withGlobalAuthed(withAuth(routes.organisationMembers)),
|
||||
"/organisation/member-time-tracking": withGlobalAuthed(
|
||||
withAuth(routes.organisationMemberTimeTracking),
|
||||
),
|
||||
"/organisation/remove-member": withGlobalAuthed(
|
||||
withAuth(withCSRF(routes.organisationRemoveMember)),
|
||||
),
|
||||
@@ -97,6 +100,16 @@ const main = async () => {
|
||||
"/timer/get": withGlobalAuthed(withAuth(withCSRF(routes.timerGet))),
|
||||
"/timer/get-inactive": withGlobalAuthed(withAuth(withCSRF(routes.timerGetInactive))),
|
||||
"/timers": withGlobalAuthed(withAuth(withCSRF(routes.timers))),
|
||||
|
||||
// subscription routes - webhook has no auth
|
||||
"/subscription/create-checkout-session": withGlobalAuthed(
|
||||
withAuth(withCSRF(routes.subscriptionCreateCheckoutSession)),
|
||||
),
|
||||
"/subscription/create-portal-session": withGlobalAuthed(
|
||||
withAuth(withCSRF(routes.subscriptionCreatePortalSession)),
|
||||
),
|
||||
"/subscription/get": withGlobalAuthed(withAuth(routes.subscriptionGet)),
|
||||
"/subscription/webhook": withGlobal(routes.subscriptionWebhook),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import organisationById from "./organisation/by-id";
|
||||
import organisationsByUser from "./organisation/by-user";
|
||||
import organisationCreate from "./organisation/create";
|
||||
import organisationDelete from "./organisation/delete";
|
||||
import organisationMemberTimeTracking from "./organisation/member-time-tracking";
|
||||
import organisationMembers from "./organisation/members";
|
||||
import organisationRemoveMember from "./organisation/remove-member";
|
||||
import organisationUpdate from "./organisation/update";
|
||||
@@ -37,6 +38,10 @@ import sprintCreate from "./sprint/create";
|
||||
import sprintDelete from "./sprint/delete";
|
||||
import sprintUpdate from "./sprint/update";
|
||||
import sprintsByProject from "./sprints/by-project";
|
||||
import subscriptionCreateCheckoutSession from "./subscription/create-checkout-session";
|
||||
import subscriptionCreatePortalSession from "./subscription/create-portal-session";
|
||||
import subscriptionGet from "./subscription/get";
|
||||
import subscriptionWebhook from "./subscription/webhook";
|
||||
import timerEnd from "./timer/end";
|
||||
import timerGet from "./timer/get";
|
||||
import timerGetInactive from "./timer/get-inactive";
|
||||
@@ -77,6 +82,7 @@ export const routes = {
|
||||
organisationUpdate,
|
||||
organisationDelete,
|
||||
organisationAddMember,
|
||||
organisationMemberTimeTracking,
|
||||
organisationMembers,
|
||||
organisationRemoveMember,
|
||||
organisationUpdateMemberRole,
|
||||
@@ -104,4 +110,9 @@ export const routes = {
|
||||
timerGetInactive,
|
||||
timerEnd,
|
||||
timers,
|
||||
|
||||
subscriptionCreateCheckoutSession,
|
||||
subscriptionCreatePortalSession,
|
||||
subscriptionGet,
|
||||
subscriptionWebhook,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { calculateBreakTimeMs, calculateWorkTimeMs, isTimerRunning } from "@sprint/shared";
|
||||
import { z } from "zod";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
getOrganisationById,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationMemberTimedSessions,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseQueryParams } from "../../validation";
|
||||
|
||||
const OrgMemberTimeTrackingQuerySchema = z.object({
|
||||
organisationId: z.coerce.number().int().positive("organisationId must be a positive integer"),
|
||||
fromDate: z.coerce.date().optional(),
|
||||
});
|
||||
|
||||
// GET /organisation/member-time-tracking?organisationId=123&fromDate=2024-01-01
|
||||
export default async function organisationMemberTimeTracking(req: AuthedRequest) {
|
||||
const url = new URL(req.url);
|
||||
const parsed = parseQueryParams(url, OrgMemberTimeTrackingQuerySchema);
|
||||
if ("error" in parsed) return parsed.error;
|
||||
|
||||
const { organisationId, fromDate } = parsed.data;
|
||||
|
||||
// Check organisation exists
|
||||
const organisation = await getOrganisationById(organisationId);
|
||||
if (!organisation) {
|
||||
return errorResponse(`organisation with id ${organisationId} not found`, "ORG_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
// Check user is admin or owner of the organisation
|
||||
const memberRole = await getOrganisationMemberRole(organisationId, req.userId);
|
||||
if (!memberRole) {
|
||||
return errorResponse("you are not a member of this organisation", "NOT_MEMBER", 403);
|
||||
}
|
||||
|
||||
const role = memberRole.role;
|
||||
if (role !== "owner" && role !== "admin") {
|
||||
return errorResponse("you must be an owner or admin to view member time tracking", "FORBIDDEN", 403);
|
||||
}
|
||||
|
||||
// Get timed sessions for all organisation members
|
||||
const sessions = await getOrganisationMemberTimedSessions(organisationId, fromDate);
|
||||
|
||||
// Enrich with calculated times
|
||||
// timestamps come from the database as strings, need to convert to Date objects for calculation
|
||||
const enriched = sessions.map((session) => {
|
||||
const timestamps = session.timestamps.map((t) => new Date(t));
|
||||
return {
|
||||
id: session.id,
|
||||
userId: session.userId,
|
||||
issueId: session.issueId,
|
||||
issueNumber: session.issueNumber,
|
||||
projectKey: session.projectKey,
|
||||
timestamps: session.timestamps, // Return original strings for JSON serialization
|
||||
endedAt: session.endedAt,
|
||||
createdAt: session.createdAt,
|
||||
workTimeMs: calculateWorkTimeMs(timestamps),
|
||||
breakTimeMs: calculateBreakTimeMs(timestamps),
|
||||
isRunning: session.endedAt === null && isTimerRunning(timestamps),
|
||||
};
|
||||
});
|
||||
|
||||
return Response.json(enriched);
|
||||
}
|
||||
Reference in New Issue
Block a user