feat(api): server-authoritative pause/resume + worked-time stop + CSV paused
Add user-scoped POST /api/sessions/:id/pause and /resume endpoints, mirroring the stop handler's ownership/lookup and 401/404/409 guards. Pause sets paused_at (status stays active); resume folds the open span into paused_seconds and clears paused_at. Change stop to fold any open pause span into paused_seconds, then set duration_seconds = max(0, round((end-start)/1000) - paused_seconds) so saved duration is worked time, and clear paused_at. Add a "Paused Duration" column to /api/export (after "Total Duration") using formatDuration(paused_seconds). Products affected: SoleLog backend (apps/api)
This commit is contained in:
@@ -27,6 +27,7 @@ sessionsRoutes.get('/api/export', async (c) => {
|
||||
'No. of Insoles',
|
||||
'Date',
|
||||
'Total Duration',
|
||||
'Paused Duration',
|
||||
'Start Time',
|
||||
'End Time',
|
||||
]
|
||||
@@ -43,6 +44,7 @@ sessionsRoutes.get('/api/export', async (c) => {
|
||||
session.pairCount ?? 2,
|
||||
start.toLocaleDateString('nl-BE', { day: '2-digit', month: '2-digit', year: 'numeric' }),
|
||||
formatDuration(session.durationSeconds ?? 0),
|
||||
formatDuration(session.pausedSeconds ?? 0),
|
||||
start.toLocaleTimeString('nl-BE', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
@@ -122,6 +124,55 @@ sessionsRoutes.post('/api/sessions/start', async (c) => {
|
||||
return c.json(toWorkSession(row, { activityName: activity.name }));
|
||||
});
|
||||
|
||||
sessionsRoutes.post('/api/sessions/:id/pause', async (c) => {
|
||||
const sessionUser = await getSessionUser(c);
|
||||
if (!sessionUser) return c.json({ error: 'Unauthorized' }, 401);
|
||||
|
||||
const id = Number.parseInt(c.req.param('id'), 10);
|
||||
if (Number.isNaN(id)) return c.json({ error: 'Session not found' }, 404);
|
||||
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(workSessions)
|
||||
.where(and(eq(workSessions.id, id), eq(workSessions.userId, sessionUser.id)));
|
||||
if (!row) return c.json({ error: 'Session not found' }, 404);
|
||||
if (row.status !== 'active') return c.json({ error: 'Session not active' }, 409);
|
||||
if (row.pausedAt) return c.json({ error: 'Already paused' }, 409);
|
||||
|
||||
const [updated] = await db
|
||||
.update(workSessions)
|
||||
.set({ pausedAt: new Date() })
|
||||
.where(eq(workSessions.id, id))
|
||||
.returning();
|
||||
return c.json(toWorkSession(updated));
|
||||
});
|
||||
|
||||
sessionsRoutes.post('/api/sessions/:id/resume', async (c) => {
|
||||
const sessionUser = await getSessionUser(c);
|
||||
if (!sessionUser) return c.json({ error: 'Unauthorized' }, 401);
|
||||
|
||||
const id = Number.parseInt(c.req.param('id'), 10);
|
||||
if (Number.isNaN(id)) return c.json({ error: 'Session not found' }, 404);
|
||||
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(workSessions)
|
||||
.where(and(eq(workSessions.id, id), eq(workSessions.userId, sessionUser.id)));
|
||||
if (!row) return c.json({ error: 'Session not found' }, 404);
|
||||
if (row.status !== 'active') return c.json({ error: 'Session not active' }, 409);
|
||||
if (!row.pausedAt) return c.json({ error: 'Not paused' }, 409);
|
||||
|
||||
const pausedSeconds =
|
||||
(row.pausedSeconds ?? 0) + Math.round((Date.now() - new Date(row.pausedAt).getTime()) / 1000);
|
||||
|
||||
const [updated] = await db
|
||||
.update(workSessions)
|
||||
.set({ pausedSeconds, pausedAt: null })
|
||||
.where(eq(workSessions.id, id))
|
||||
.returning();
|
||||
return c.json(toWorkSession(updated));
|
||||
});
|
||||
|
||||
sessionsRoutes.post('/api/sessions/:id/stop', async (c) => {
|
||||
const sessionUser = await getSessionUser(c);
|
||||
if (!sessionUser) return c.json({ error: 'Unauthorized' }, 401);
|
||||
@@ -136,14 +187,18 @@ sessionsRoutes.post('/api/sessions/:id/stop', async (c) => {
|
||||
if (!row) return c.json({ error: 'Session not found' }, 404);
|
||||
if (row.status !== 'active') return c.json({ error: 'Session already closed' }, 409);
|
||||
|
||||
const endTime = new Date();
|
||||
const durationSeconds = Math.round(
|
||||
(endTime.getTime() - new Date(row.startTime).getTime()) / 1000
|
||||
);
|
||||
const now = Date.now();
|
||||
const extraPaused = row.pausedAt
|
||||
? Math.round((now - new Date(row.pausedAt).getTime()) / 1000)
|
||||
: 0;
|
||||
const pausedSeconds = (row.pausedSeconds ?? 0) + extraPaused;
|
||||
const endTime = new Date(now);
|
||||
const wall = Math.round((now - new Date(row.startTime).getTime()) / 1000);
|
||||
const durationSeconds = Math.max(0, wall - pausedSeconds);
|
||||
|
||||
const [updated] = await db
|
||||
.update(workSessions)
|
||||
.set({ endTime, durationSeconds, status: 'completed' })
|
||||
.set({ endTime, durationSeconds, pausedSeconds, pausedAt: null, status: 'completed' })
|
||||
.where(eq(workSessions.id, id))
|
||||
.returning();
|
||||
return c.json(toWorkSession(updated));
|
||||
|
||||
Reference in New Issue
Block a user