From fa72ff15a3f63e3b68e0d670241fef5418c8b56a Mon Sep 17 00:00:00 2001 From: Lucas Birkert Date: Fri, 9 Aug 2024 19:14:12 +0200 Subject: [PATCH 1/5] Allow the bot to count minutes even when down --- .../20240809171401_timestamps/migration.sql | 2 + prisma/schema.prisma | 4 +- src/clock.ts | 77 +++++++++---------- src/extensions/api/index.ts | 26 +++---- src/extensions/arcade/slack/views/sessions.ts | 18 ++--- src/extensions/arcade/watchers/hackhour.ts | 32 ++++---- src/extensions/slack/functions/extend.ts | 28 +++---- src/extensions/slack/functions/goals.ts | 38 ++++----- src/extensions/slack/functions/pause.ts | 30 ++++---- src/extensions/slack/index.ts | 23 +++--- src/extensions/slack/lib/lib.ts | 12 +-- src/extensions/slack/views/controller.ts | 18 ++--- src/extensions/slack/views/topLevel.ts | 10 +-- src/lib/corelib.ts | 20 +++-- src/lib/emitter.ts | 8 +- src/lib/lock.ts | 2 +- src/lib/prisma.ts | 29 ++++++- 17 files changed, 204 insertions(+), 173 deletions(-) create mode 100644 prisma/migrations/20240809171401_timestamps/migration.sql diff --git a/prisma/migrations/20240809171401_timestamps/migration.sql b/prisma/migrations/20240809171401_timestamps/migration.sql new file mode 100644 index 00000000..6e1de6eb --- /dev/null +++ b/prisma/migrations/20240809171401_timestamps/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Session" ADD COLUMN "resumedOrPausedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d2af9983..2f32af40 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -54,6 +54,8 @@ model Session { time Int elapsed Int elapsedSincePause Int + // this timestamp is from when the session was last resumed or paused + resumedOrPausedAt DateTime @default(now()) completed Boolean cancelled Boolean @@ -130,4 +132,4 @@ model Scrapbook { createdAt DateTime @default(now()) sessions Session[] -} \ No newline at end of file +} diff --git a/src/clock.ts b/src/clock.ts index b3fc736d..1c28ccc2 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -1,4 +1,4 @@ -import { prisma } from "./lib/prisma.js"; +import { getElapsed, getElapsedSincePaused, prisma } from "./lib/prisma.js"; import { emitter } from "./lib/emitter.js"; import { Constants } from "./lib/constants.js"; @@ -22,22 +22,12 @@ emitter.on('minute', async () => { } for (const session of sessions) { - let updatedSession = session; - if (session.paused) { - updatedSession = await prisma.session.update({ - where: { - id: session.id - }, - data: { - elapsedSincePause: { - increment: 1 - } - } - }); + // in minutes + let elapsedSincePause = getElapsedSincePaused(session); - if (updatedSession.elapsedSincePause > Constants.AUTO_CANCEL) { - updatedSession = await prisma.session.update({ + if (elapsedSincePause > Constants.AUTO_CANCEL) { + await prisma.session.update({ where: { id: session.id }, @@ -47,33 +37,25 @@ emitter.on('minute', async () => { } }); - emitter.emit('cancel', updatedSession); + emitter.emit('cancel', session); } else { if (updateWithRatelimit) { - if (updatedSession.elapsedSincePause % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); + if (elapsedSincePause % 5 === 0) { + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: false }); } } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); } } continue; - } else { - updatedSession = await prisma.session.update({ - where: { - id: session.id - }, - data: { - elapsed: { - increment: 1 - } - } - }); } + let elapsed = getElapsed(session); + + await prisma.user.update({ where: { id: session.userId @@ -85,26 +67,41 @@ emitter.on('minute', async () => { } }); - if (updatedSession.elapsed >= updatedSession.time) { // TODO: Commit hours to goal, verify hours with events - updatedSession = await prisma.session.update({ + if (elapsed >= session.time) { // TODO: Commit hours to goal, verify hours with events + await prisma.session.update({ where: { id: session.id }, data: { - completed: true + completed: true, + elapsed: { + set: session.time + } + } + }); + + // update lifetime minutes + await prisma.user.update({ + where: { + id: session.userId + }, + data: { + lifetimeMinutes: { + increment: session.time + }, } }); - emitter.emit('complete', updatedSession); + emitter.emit('complete', session); } else { if (updateWithRatelimit) { - if (updatedSession.elapsed % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); + if (elapsed % 5 === 0) { + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: false }); } } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); } } } @@ -141,4 +138,4 @@ emitter.on('error', async (errorRef) => { emitter.on('init', async () => { console.log('[Clock] 🕒 Core Subroutine Initialized!'); -}); \ No newline at end of file +}); diff --git a/src/extensions/api/index.ts b/src/extensions/api/index.ts index 6f250dc3..24277fcc 100644 --- a/src/extensions/api/index.ts +++ b/src/extensions/api/index.ts @@ -1,4 +1,4 @@ -import { prisma, uid } from "../../lib/prisma.js"; +import { getElapsed, prisma, uid } from "../../lib/prisma.js"; import { Slack, app, express } from "../../lib/bolt.js"; import { AirtableAPI } from "../../lib/airtable.js"; @@ -72,7 +72,7 @@ declare global { // createdAt: session.createdAt, // endedAt: new Date(), // time: session.time, -// elapsed: session.elapsed, +// elapsed: getElapsed(session), // completed: session.completed, // cancelled: session.cancelled, // paused: session.paused, @@ -90,7 +90,7 @@ declare global { // await postEndpoints(session); // }); -express.set('trust proxy', true) +express.set('trust proxy', true) express.use((req, res, next) => { const authHeader = req.headers['authorization']; @@ -174,16 +174,13 @@ express.get('/api/clock/:slackId', readLimit, async (req, res) => { userId: slackUser.userId, completed: false, cancelled: false, - paused: false, + // paused: false, }, }); if (result) { - const startTime = result.createdAt.getTime(); - const duration = result.time * 60 * 1000; // convert from minutes to milliseconds - const currTime = new Date().getTime(); - const elapsedTime = currTime - startTime; - const leftTime = duration - elapsedTime; + const elapsed = getElapsed(result); + const leftTime = result.time - elapsed; return res.status(200).send(leftTime.toString()); } else { return res.status(200).send((-1).toString()); @@ -235,7 +232,8 @@ express.get('/api/session/:slackId', readLimit, async (req, res) => { if (result) { const now = new Date(); - const endTime = new Date(now.getTime() + (result.time - result.elapsed) * 60 * 1000); + const elapsed = getElapsed(result); + const endTime = new Date(now.getTime() + (result.time - elapsed) * 60 * 1000); endTime.setMilliseconds(0); endTime.setSeconds(0); @@ -246,8 +244,8 @@ express.get('/api/session/:slackId', readLimit, async (req, res) => { id: slackUser.slackId, createdAt: result.createdAt, time: result.time, - elapsed: result.elapsed, - remaining: result.time - result.elapsed, + elapsed: getElapsed(result), + remaining: result.time - elapsed, endTime: endTime, paused: result.paused, completed: result.completed || result.cancelled, @@ -424,7 +422,7 @@ express.get('/api/history/:slackId', readLimit, async (req, res) => { return { createdAt: r.createdAt, time: r.time, - elapsed: r.elapsed, + elapsed: getElapsed(r), goal: r.goal.name, ended: r.completed || r.cancelled, @@ -704,4 +702,4 @@ express.post('/api/pause/:slackId', limiter, async (req, res) => { } catch (error) { emitter.emit('error', { error }); } -}); \ No newline at end of file +}); diff --git a/src/extensions/arcade/slack/views/sessions.ts b/src/extensions/arcade/slack/views/sessions.ts index 5190b7be..a6472570 100644 --- a/src/extensions/arcade/slack/views/sessions.ts +++ b/src/extensions/arcade/slack/views/sessions.ts @@ -4,7 +4,7 @@ import { Slack } from "../../../../lib/bolt.js"; import { Actions, Environment } from "../../../../lib/constants.js"; import { AirtableAPI } from "../../../../lib/airtable.js"; import { formatHour, pfps } from "../../../../lib/templates.js"; -import { prisma } from "../../../../lib/prisma.js"; +import { getElapsed, prisma } from "../../../../lib/prisma.js"; import { Loading } from "../../../slack/views/loading.js"; export class Sessions { @@ -45,7 +45,7 @@ export class Sessions { text: { type: "plain_text", text: -`You have ${airtableUser.fields['Balance (Hours)']} :tw_admission_tickets: remaining.`, + `You have ${airtableUser.fields['Balance (Hours)']} :tw_admission_tickets: remaining.`, emoji: true } }, { @@ -74,8 +74,8 @@ _Lifetime minutes: ${airtableUser.fields['Minutes (All)']} minutes (${formatHour type: "section", text: { type: "mrkdwn", - text: -`Tickets spent: ${Math.floor(airtableUser.fields['Spent Incl. Pending (Minutes)'] / 60)} :tw_admission_tickets:` + text: + `Tickets spent: ${Math.floor(airtableUser.fields['Spent Incl. Pending (Minutes)'] / 60)} :tw_admission_tickets:` } }, { "type": "actions", @@ -133,7 +133,7 @@ _Lifetime minutes: ${airtableUser.fields['Minutes (All)']} minutes (${formatHour type: "section", text: { type: "mrkdwn", - text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${session.elapsed} minutes\n${session.metadata?.work}\n*Status: In Progress*\n<${permalink?.permalink}|View Session>` + text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${getElapsed((session))} minutes\n${session.metadata?.work}\n*Status: In Progress*\n<${permalink?.permalink}|View Session>` } }, { type: "divider" @@ -146,7 +146,7 @@ _Lifetime minutes: ${airtableUser.fields['Minutes (All)']} minutes (${formatHour type: "section", text: { type: "mrkdwn", - text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${session.elapsed} minutes\n${session.metadata?.work}\n*Status: Not Found*\n<${permalink?.permalink}|View Session>` + text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${getElapsed(session)} minutes\n${session.metadata?.work}\n*Status: Not Found*\n<${permalink?.permalink}|View Session>` } }, { type: "divider" @@ -162,7 +162,7 @@ _Lifetime minutes: ${airtableUser.fields['Minutes (All)']} minutes (${formatHour type: "section", text: { type: "mrkdwn", - text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${session.elapsed} minutes\n${session.metadata?.work}\n*Status: Not Found*\n<${permalink?.permalink}|View Session>` + text: `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${getElapsed(session)} minutes\n${session.metadata?.work}\n*Status: Not Found*\n<${permalink?.permalink}|View Session>` } }, { type: "divider" @@ -183,7 +183,7 @@ _Lifetime minutes: ${airtableUser.fields['Minutes (All)']} minutes (${formatHour text: { type: "mrkdwn", text: - `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${session.elapsed} minutes + `*${session.createdAt.getMonth() + 1}/${session.createdAt.getDate()}* - ${getElapsed(session)} minutes ${session.metadata?.work} *Status: ${airtableSession.fields["Status"]}*${airtableSession.fields["Reason"] ? `- ${airtableSession.fields["Reason"]}` : ""} ${approved ? `*Has Scrapbook?*: ${banked ? `yup!` : `noo ):`}` : ``} @@ -251,4 +251,4 @@ ${approved ? `*Has Scrapbook?*: ${banked ? `yup!` : `noo ):`}` : ``} }), } } -} \ No newline at end of file +} diff --git a/src/extensions/arcade/watchers/hackhour.ts b/src/extensions/arcade/watchers/hackhour.ts index 5a6f42a8..c7d2e046 100644 --- a/src/extensions/arcade/watchers/hackhour.ts +++ b/src/extensions/arcade/watchers/hackhour.ts @@ -1,4 +1,4 @@ -import { prisma } from "../../../lib/prisma.js"; +import { getElapsed, prisma } from "../../../lib/prisma.js"; import { Session, User } from "@prisma/client"; import { AirtableAPI } from "../../../lib/airtable.js"; import { app, Slack } from "../../../lib/bolt.js"; @@ -71,7 +71,7 @@ const findOrCreateUser = async (userId: string) => { return user; } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }; @@ -109,9 +109,9 @@ const registerSession = async (session: Session) => { console.log(`[registerSession] Fetched or created user ${user.metadata.airtable.id}`); - const { activity, evidenced } = await Evidence.check({ - messageTs: session.messageTs, - slackId: user.slackUser!.slackId + const { activity, evidenced } = await Evidence.check({ + messageTs: session.messageTs, + slackId: user.slackUser!.slackId }); const permalink = await Slack.chat.getPermalink({ @@ -129,7 +129,7 @@ const registerSession = async (session: Session) => { "Control TS": session.controlTs, "User": [user.metadata.airtable.id], "Work": (session.metadata as any).work, - "Minutes": session.elapsed, + "Minutes": getElapsed(session), "Status": "Unreviewed", "Created At": session.createdAt.toISOString(), "Activity": activity, @@ -175,7 +175,7 @@ const registerSession = async (session: Session) => { }); } } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }; @@ -212,7 +212,7 @@ app.event("message", async ({ event }) => { await surfaceEvidence(thread_ts, session.user.slackUser!.slackId); - if (!session.metadata.airtable) { + if (!session.metadata.airtable) { if (session.metadata.firstTime && session.user.metadata.airtable) { // Use this as an alternative flow - the user is learning how hack hour works const airtableUser = await AirtableAPI.User.find(session.user.metadata.airtable.id); @@ -227,7 +227,7 @@ app.event("message", async ({ event }) => { "type": "mrkdwn", "text": t('firstTime.walkthrough.complete', { slackId: session.user.slackUser!.slackId, - minutes: session.elapsed + minutes: getElapsed(session) }) }, "accessory": { @@ -256,14 +256,14 @@ app.event("message", async ({ event }) => { return; } - + // throw new Error(`Session ${session.id} is missing an Airtable ID`); return; } if (session) { const airtableSession = await AirtableAPI.Session.find(session.metadata.airtable.id); - + if (!airtableSession) { const permalink = (await Slack.chat.getPermalink({ channel: Environment.MAIN_CHANNEL, @@ -285,7 +285,7 @@ app.event("message", async ({ event }) => { }); const { activity, evidenced, image } = await Evidence.check({ - messageTs: session.messageTs, + messageTs: session.messageTs, slackId: user.slackUser!.slackId }); @@ -350,7 +350,7 @@ app.event("message", async ({ event }) => { // } // }); -// if ((session.time - session.elapsed) % 15 == 0 && session.elapsed > 0 && session.metadata.firstTime) { +// if ((session.time - getElapsed(session)) % 15 == 0 && getElapsed(session) > 0 && session.metadata.firstTime) { // // Send a reminder every 15 minutes // await Slack.chat.postMessage({ // thread_ts: session.messageTs, @@ -358,7 +358,7 @@ app.event("message", async ({ event }) => { // channel: Environment.MAIN_CHANNEL, // text: t(`onboarding.update`, { // slackId: slackUser.slackId, -// minutes: session.time - session.elapsed +// minutes: session.time - getElapsed(session) // }) // }); // } @@ -460,7 +460,7 @@ emitter.on('start', async (session: Session) => { } }); - + if (!user.metadata.airtable) { throw new Error(`Airtable user not found for ${user.id}`); } @@ -487,6 +487,6 @@ emitter.on('start', async (session: Session) => { }); } } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); diff --git a/src/extensions/slack/functions/extend.ts b/src/extensions/slack/functions/extend.ts index 3c212b73..538bb07e 100644 --- a/src/extensions/slack/functions/extend.ts +++ b/src/extensions/slack/functions/extend.ts @@ -10,7 +10,7 @@ Time Extension */ Slack.action(Actions.EXTEND, async ({ body }) => { // TODO -// informUser(body.user.id, `Use \`${Commands.EXTEND}\` to extend the amount of time you have!`, Environment.MAIN_CHANNEL, (body as any).message.thread_ts); + // informUser(body.user.id, `Use \`${Commands.EXTEND}\` to extend the amount of time you have!`, Environment.MAIN_CHANNEL, (body as any).message.thread_ts); informUser(body.user.id, `This command is disabled for now!`, Environment.MAIN_CHANNEL, (body as any).message.thread_ts); }); @@ -21,9 +21,9 @@ Slack.command(Commands.EXTEND, async ({ body }) => { // Disable extend for now informUser(body.user_id, `This command is disabled for now!`, body.channel_id); return; - + const slackId = body.user_id; - + const session = await prisma.session.findFirst({ where: { user: { @@ -33,7 +33,7 @@ Slack.command(Commands.EXTEND, async ({ body }) => { }, completed: false, cancelled: false, - } + } }); if (!session) { @@ -56,15 +56,15 @@ Slack.command(Commands.EXTEND, async ({ body }) => { minutes = 60; } -/* - const updatedSession = await Session.extend(session, minutes); - - informUser(slackId, `Session extended by ${minutes} minutes! Remaining time: ${updatedSession.time-updatedSession.elapsed} out of ${updatedSession.time} minutes`, body.channel_id); - - // Update the session ts - await updateController(updatedSession); - await updateTopLevel(updatedSession);*/ + /* + const updatedSession = await Session.extend(session, minutes); + + informUser(slackId, `Session extended by ${minutes} minutes! Remaining time: ${updatedSession.time-updatedgetElapsed(session)} out of ${updatedSession.time} minutes`, body.channel_id); + + // Update the session ts + await updateController(updatedSession); + await updateTopLevel(updatedSession);*/ } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } -}); \ No newline at end of file +}); diff --git a/src/extensions/slack/functions/goals.ts b/src/extensions/slack/functions/goals.ts index 3eb15c21..b4309f7e 100644 --- a/src/extensions/slack/functions/goals.ts +++ b/src/extensions/slack/functions/goals.ts @@ -1,5 +1,5 @@ import { Slack } from "../../../lib/bolt.js"; -import { prisma, uid } from "../../../lib/prisma.js"; +import { getElapsed, prisma, uid } from "../../../lib/prisma.js"; import { Goals } from "../views/goals.js"; import { Actions, Callbacks } from "../../../lib/constants.js"; @@ -12,7 +12,7 @@ Slack.action(Actions.OPEN_GOAL, async ({ body, client }) => { try { const slackId: string = body.user.id; const trigger_id: string = (body as any).trigger_id - + const view = await Slack.views.open({ trigger_id: trigger_id, view: Loading.loading() @@ -30,7 +30,7 @@ Slack.action(Actions.OPEN_GOAL, async ({ body, client }) => { } } }); - + if (!session.user.slackUser) { throw new Error(`Slack user not found`); } @@ -59,7 +59,7 @@ Slack.action(Actions.OPEN_GOAL, async ({ body, client }) => { view: await Goals.main(session.id) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -109,7 +109,7 @@ Slack.action(Actions.SELECT_GOAL, async ({ body, client }) => { }, data: { minutes: { - decrement: session.elapsed + decrement: getElapsed(session) } } }); @@ -120,7 +120,7 @@ Slack.action(Actions.SELECT_GOAL, async ({ body, client }) => { }, data: { minutes: { - increment: session.elapsed + increment: getElapsed(session) } } }); @@ -134,11 +134,11 @@ Slack.action(Actions.SELECT_GOAL, async ({ body, client }) => { view: await Goals.main(session.id) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); -Slack.view(Callbacks.MAIN_GOAL, async ({}) => {}); +Slack.view(Callbacks.MAIN_GOAL, async ({ }) => { }); Slack.action(Actions.CREATE_GOAL, async ({ body, client }) => { try { @@ -150,7 +150,7 @@ Slack.action(Actions.CREATE_GOAL, async ({ body, client }) => { view: await Goals.create(sessionId) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -180,10 +180,10 @@ Slack.view(Callbacks.CREATE_GOAL, async ({ body, view, client }) => { await client.views.update({ view_id: body.view.root_view_id!, view: await Goals.main(sessionId, 'Please enter a goal name') - }); + }); // updating views is broken - + return; } @@ -235,7 +235,7 @@ Slack.view(Callbacks.CREATE_GOAL, async ({ body, view, client }) => { view: await Goals.main(sessionId) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -273,7 +273,7 @@ Slack.action(Actions.DELETE_GOAL, async ({ body, client }) => { view: await Goals.delete(sessionId) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -335,7 +335,7 @@ Slack.view(Callbacks.DELETE_GOAL, async ({ body, view, client }) => { (await prisma.session.findMany({ where: { goal: { - id: oldGoal.id + id: oldGoal.id } } })).forEach(async (session) => { @@ -351,7 +351,7 @@ Slack.view(Callbacks.DELETE_GOAL, async ({ body, view, client }) => { }, data: { minutes: { - decrement: session.elapsed + decrement: getElapsed(session) } } }); @@ -362,11 +362,11 @@ Slack.view(Callbacks.DELETE_GOAL, async ({ body, view, client }) => { }, data: { minutes: { - increment: session.elapsed + increment: getElapsed(session) } } }); - } + } updateController(session); updateTopLevel(session); @@ -376,6 +376,6 @@ Slack.view(Callbacks.DELETE_GOAL, async ({ body, view, client }) => { view: await Goals.main(sessionId) }); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } -}); \ No newline at end of file +}); diff --git a/src/extensions/slack/functions/pause.ts b/src/extensions/slack/functions/pause.ts index b58477b6..fa7b4e94 100644 --- a/src/extensions/slack/functions/pause.ts +++ b/src/extensions/slack/functions/pause.ts @@ -3,7 +3,7 @@ Pause Management */ import { Slack } from "../../../lib/bolt.js"; import { Environment, Actions, Commands } from "../../../lib/constants.js"; -import { prisma } from "../../../lib/prisma.js"; +import { getElapsed, prisma } from "../../../lib/prisma.js"; import { emitter } from "../../../lib/emitter.js"; import { Session } from "../../../lib/corelib.js"; @@ -40,14 +40,14 @@ Slack.action(Actions.PAUSE, async ({ body }) => { channel: Environment.MAIN_CHANNEL, text: t(`error.not_yours`), thread_ts: (body as any).message.thread_ts - }); + }); return; } await Session.pause(session); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -65,7 +65,7 @@ Slack.action(Actions.RESUME, async ({ body }) => { }); if (!session) { - informUser(slackId, t('error.not_yours'), Environment.MAIN_CHANNEL, (body as any).message.thread_ts, pfps['threat']); + informUser(slackId, t('error.not_yours'), Environment.MAIN_CHANNEL, (body as any).message.thread_ts, pfps['threat']); return; } @@ -80,7 +80,7 @@ Slack.action(Actions.RESUME, async ({ body }) => { await Session.pause(session); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -113,16 +113,16 @@ Slack.command(Commands.PAUSE, async ({ body }) => { const updatedSession = await Session.pause(session); const toggleMessage = updatedSession.paused ? - t('action.paused', { - minutes: updatedSession.time - updatedSession.elapsed - }) : - t('action.resumed', { - minutes: updatedSession.time - updatedSession.elapsed - }); + t('action.paused', { + minutes: updatedSession.time - getElapsed(updatedSession) + }) : + t('action.resumed', { + minutes: updatedSession.time - getElapsed(updatedSession) + }); informUser(slackId, toggleMessage, body.channel_id); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } }); @@ -160,9 +160,9 @@ Slack.command(Commands.START, async ({ body }) => { // Send a message to the user in the channel they ran the command informUser(slackId, t('action.resumed', { - minutes: updatedSession.time - updatedSession.elapsed + minutes: updatedSession.time - getElapsed(updatedSession) }), body.channel_id); } catch (error) { - emitter.emit('error', {error}); + emitter.emit('error', { error }); } -}); \ No newline at end of file +}); diff --git a/src/extensions/slack/index.ts b/src/extensions/slack/index.ts index 04dc118f..b9508a48 100644 --- a/src/extensions/slack/index.ts +++ b/src/extensions/slack/index.ts @@ -1,6 +1,6 @@ import { app, Slack } from "../../lib/bolt.js"; import { Actions, Commands, Constants, Environment } from "../../lib/constants.js"; -import { prisma, uid } from "../../lib/prisma.js"; +import { getElapsed, prisma, uid } from "../../lib/prisma.js"; import { emitter } from "../../lib/emitter.js"; import { t, t_fetch, t_format } from "../../lib/templates.js"; @@ -487,13 +487,14 @@ emitter.on('sessionUpdate', async (update: { // } // } + const elapsed = getElapsed(session); if (session.paused) { if (updateSlack) { await updateController(session); } return; - } else if ((session.time - session.elapsed) % 15 == 0 && session.elapsed > 0 && !session.metadata.firstTime) { + } else if ((session.time - elapsed) % 15 == 0 && elapsed > 0 && !session.metadata.firstTime) { // Send a reminder every 15 minutes await Slack.chat.postMessage({ thread_ts: session.messageTs, @@ -501,7 +502,7 @@ emitter.on('sessionUpdate', async (update: { channel: Environment.MAIN_CHANNEL, text: t(`update`, { slackId: slackUser.slackId, - minutes: session.time - session.elapsed + minutes: elapsed }) }); @@ -575,7 +576,7 @@ emitter.on('complete', async (session: Session) => { }, data: { minutes: { - increment: session.elapsed + increment: getElapsed(session) } } }); @@ -619,14 +620,14 @@ emitter.on('cancel', async (session: Session) => { channel: Environment.MAIN_CHANNEL, text: /*session.metadata.firstTime ? t('onboarding.complete', { slackId: slackUser.slackId, - minutes: session.elapsed + minutes: getElapsed(session) }) :*/t('cancel', { slackId: slackUser.slackId, - minutes: session.elapsed + minutes: getElapsed(session) }), // text: t_format('hey <@${slackId}>! you cancelled your hour, but you still have ${minutes} minutes recorded - make sure to post something to count those!', { // slackId: slackUser.slackId, - // minutes: session.elapsed + // minutes: getElapsed(session) // }), blocks: [ { @@ -635,10 +636,10 @@ emitter.on('cancel', async (session: Session) => { "type": "mrkdwn", "text": /*session.metadata.firstTime ? t('onboarding.complete', { slackId: slackUser.slackId, - minutes: session.elapsed + minutes: getElapsed(session) }) :*/ t('cancel', { slackId: slackUser.slackId, - minutes: session.elapsed + minutes: getElapsed(session) }) }, "accessory": { @@ -661,7 +662,7 @@ emitter.on('cancel', async (session: Session) => { }, data: { minutes: { - increment: session.elapsed + increment: getElapsed(session) } } }); @@ -818,4 +819,4 @@ emitter.on('debug', async (message) => { } catch (error) { emitter.emit('error', { error }); } -}); \ No newline at end of file +}); diff --git a/src/extensions/slack/lib/lib.ts b/src/extensions/slack/lib/lib.ts index 72766821..a803339a 100644 --- a/src/extensions/slack/lib/lib.ts +++ b/src/extensions/slack/lib/lib.ts @@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client"; import { Constants, Environment } from "../../../lib/constants.js"; import { app, Slack } from "../../../lib/bolt.js"; -import { prisma } from "../../../lib/prisma.js"; +import { getElapsed, prisma } from "../../../lib/prisma.js"; import { t } from "../../../lib/templates.js"; import { Controller } from "../views/controller.js"; @@ -18,7 +18,7 @@ export async function updateController(session: Session) { ts: session.controlTs, channel: Environment.MAIN_CHANNEL, blocks: await Controller.panel(session), - text: `Time Remaining: ${session.time-session.elapsed} minutes - ${(() => { + text: `Time Remaining: ${session.time - getElapsed(session)} minutes - ${(() => { if (session.paused) { return "Paused"; } else if (session.cancelled) { @@ -88,7 +88,7 @@ export async function informUser(slackId: string, message: string, channel: stri if (response.error !== 'channel_not_found') { // Error not caused by access perms - emitter.emit('error', {error}); + emitter.emit('error', { error }); } } } @@ -115,8 +115,8 @@ export async function informUserBlocks(slackId: string, blocks: any[], channel: if (response.error !== 'channel_not_found') { // Error not caused by access perms - emitter.emit('error', {error}); + emitter.emit('error', { error }); } } - -} \ No newline at end of file + +} diff --git a/src/extensions/slack/views/controller.ts b/src/extensions/slack/views/controller.ts index 12b6b424..3b3a541b 100644 --- a/src/extensions/slack/views/controller.ts +++ b/src/extensions/slack/views/controller.ts @@ -1,5 +1,5 @@ import { Session } from "@prisma/client"; -import { prisma } from "../../../lib/prisma.js" +import { getElapsed, getElapsedSincePaused, prisma } from "../../../lib/prisma.js" import { t, formatHour, t_format, templates } from "../../../lib/templates.js"; import { Constants, Actions, Environment } from "../../../lib/constants.js"; import { app, Slack } from "../../../lib/bolt.js"; @@ -46,16 +46,16 @@ export class Controller { if (session.metadata.firstTime) { info.text.text = t('firstTime.controller') } else if (session.paused) { - info.text.text = `You have paused your session. You have \`${session.time - session.elapsed}\` minutes remaining. \`${Constants.AUTO_CANCEL - session.elapsedSincePause}\` minutes until the session is ended early.` + info.text.text = `You have paused your session. You have \`${session.time - getElapsed(session)}\` minutes remaining. \`${Constants.AUTO_CANCEL - getElapsedSincePaused(session)}\` minutes until the session is ended early.` } else if (session.cancelled) { info.text.text = `You have ended your session early.` } else if (session.completed) { info.text.text = t(`complete`, { slackId: slackUser.slackId }) } else { info.text.text = t_format(session.metadata.slack.controllerTemplate, { - minutes: session.time - session.elapsed, + minutes: session.time - getElapsed(session), }) - // info.text.text = `You have \`${session.time - session.elapsed}\` minutes remaining! ${t('encouragement')}` + // info.text.text = `You have \`${session.time - getElapsed(session)}\` minutes remaining! ${t('encouragement')}` } if (session.metadata.firstTime) { @@ -63,11 +63,11 @@ export class Controller { info as KnownBlock, { "type": "divider" - } + } ]; if (session.metadata.firstTime.step === 0) { - blocks.push( + blocks.push( { "type": "actions", "elements": [ @@ -236,13 +236,13 @@ export class Controller { }; if (session.paused) { - info.text.text = `You have paused your session. You have \`${session.time - session.elapsed}\` minutes remaining. \`${Constants.AUTO_CANCEL - session.elapsedSincePause}\` minutes until the session is ended early.` + info.text.text = `You have paused your session. You have \`${session.time - getElapsed(session)}\` minutes remaining. \`${Constants.AUTO_CANCEL - getElapsedSincePaused(session)}\` minutes until the session is ended early.` } else if (session.cancelled) { info.text.text = `You have ended your session early.` } else if (session.completed) { info.text.text = t(`complete`, { slackId: slackUser.slackId }) } else { - info.text.text = `You have \`${session.time - session.elapsed}\` minutes remaining! ${t('encouragement')}` + info.text.text = `You have \`${session.time - getElapsed(session)}\` minutes remaining! ${t('encouragement')}` } const permalink = await Slack.chat.getPermalink({ @@ -310,4 +310,4 @@ export class Controller { ] } }*/ -} \ No newline at end of file +} diff --git a/src/extensions/slack/views/topLevel.ts b/src/extensions/slack/views/topLevel.ts index c61401bd..daa4726c 100644 --- a/src/extensions/slack/views/topLevel.ts +++ b/src/extensions/slack/views/topLevel.ts @@ -1,5 +1,5 @@ import { Session } from "@prisma/client"; -import { prisma } from "../../../lib/prisma.js" +import { getElapsed, prisma } from "../../../lib/prisma.js" import { formatHour, t, t_format } from "../../../lib/templates.js"; export class TopLevel { @@ -37,7 +37,7 @@ export class TopLevel { } else if (session.completed) { topLevelMessage.text.text = t('complete', { slackId: slackUser?.slackId }) } else { - topLevelMessage.text.text = t_format(metadata.slack.template, { slackId: slackUser?.slackId, minutes: session.time - session.elapsed }); + topLevelMessage.text.text = t_format(metadata.slack.template, { slackId: slackUser?.slackId, minutes: session.time - getElapsed(session) }); } blocks.push(topLevelMessage); @@ -105,7 +105,7 @@ export class TopLevel { alt_text: "attachment" }); } - + blocks.push({ type: "context", elements: [ @@ -115,7 +115,7 @@ export class TopLevel { } ] }); - + return blocks; } -} \ No newline at end of file +} diff --git a/src/lib/corelib.ts b/src/lib/corelib.ts index cbcd786c..e6705abb 100644 --- a/src/lib/corelib.ts +++ b/src/lib/corelib.ts @@ -1,7 +1,7 @@ // Library for interacting with hack hour import type { Session as SessionType } from "@prisma/client"; -import { prisma } from "./prisma.js"; +import { getElapsed, prisma } from "./prisma.js"; import { emitter } from "./emitter.js"; interface SessionAction { @@ -24,7 +24,10 @@ export class Session { id: session.id }, data: { - cancelled: true + cancelled: true, + elapsed: { + set: getElapsed(session) + } } }); @@ -43,16 +46,21 @@ export class Session { }, data: { paused: !session.paused, - elapsedSincePause: session.paused ? 0 : session.elapsedSincePause + resumedOrPausedAt: { + set: new Date() + }, + elapsed: { + set: getElapsed(session) + } } }); - + if (updatedSession.paused) { emitter.emit('pause', updatedSession); } else { emitter.emit('resume', updatedSession); } - + return updatedSession; } @@ -78,4 +86,4 @@ export class Session { } // TODO: Metadata management -// TODO: Hack Hour app tokens, permissions, and authorization \ No newline at end of file +// TODO: Hack Hour app tokens, permissions, and authorization diff --git a/src/lib/emitter.ts b/src/lib/emitter.ts index a61937b4..cb28f4da 100644 --- a/src/lib/emitter.ts +++ b/src/lib/emitter.ts @@ -24,7 +24,7 @@ type EventMap = { cancel: (session: Session) => void, pause: (session: Session) => void, resume: (session: Session) => void, - + firstTime: (user: User) => void, } @@ -51,9 +51,9 @@ class Emitter { if (!this.listeners[event]) { return; } - this.listeners[event]!.forEach(listener => { + this.listeners[event]!.forEach(listener => { try { - listener(...args) + listener(...args) } catch (error) { this.emit("error", error); console.error(error); @@ -84,4 +84,4 @@ emitter.on("init", async () => { emitter.emit("hour"); }, Constants.HOUR_MS); }, Constants.HOUR_MS - Date.now() % Constants.HOUR_MS); -}); \ No newline at end of file +}); diff --git a/src/lib/lock.ts b/src/lib/lock.ts index 8f4a7a81..3e6140d2 100644 --- a/src/lib/lock.ts +++ b/src/lib/lock.ts @@ -1,3 +1,3 @@ import AsyncLock from 'async-lock'; -export const lock = new AsyncLock(); \ No newline at end of file +export const lock = new AsyncLock(); diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index af4d587e..3719dabd 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,4 +1,4 @@ -import pkg from '@prisma/client'; +import pkg, { type Session } from '@prisma/client'; const { PrismaClient } = pkg import cuid2 from '@paralleldrive/cuid2'; @@ -20,7 +20,7 @@ declare global { firstTime?: { step: number } - banked: boolean, + banked: boolean, } type UserMetadata = { airtable?: { @@ -56,4 +56,27 @@ export const prisma = new PrismaClient().$extends({ cuid2.init(); -export const uid = () => { return cuid2.createId() }; \ No newline at end of file +export const uid = () => { return cuid2.createId() }; + +// This method provides a safe way to get the elapsed time from a session. +// +// This method returns minutes. +export function getElapsed(session: Session): number { + if (session.cancelled || session.completed) { + return session.elapsed; + } + + return Math.min(session.time, session.elapsed + (session.paused ? 0 : (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000)); +} + +// This method provides a safe way to get the elapsed pause time from a session. +// +// This method returns minutes. +export function getElapsedSincePaused(session: Session) { + if (!session.paused) { + console.error("getElapsedSincePaused has been called on a session that is not paused. This is likely a mistake."); + return 0; + } + + return (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000; +} From bfc7d781c7880b39755485d854eabd6831c7ccf1 Mon Sep 17 00:00:00 2001 From: Lucas Birkert Date: Fri, 9 Aug 2024 19:20:15 +0200 Subject: [PATCH 2/5] Readd updatedSession let --- src/clock.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/clock.ts b/src/clock.ts index 1c28ccc2..24ab7c82 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -22,12 +22,14 @@ emitter.on('minute', async () => { } for (const session of sessions) { + let updatedSession = session; + if (session.paused) { // in minutes let elapsedSincePause = getElapsedSincePaused(session); if (elapsedSincePause > Constants.AUTO_CANCEL) { - await prisma.session.update({ + updatedSession = await prisma.session.update({ where: { id: session.id }, @@ -37,16 +39,16 @@ emitter.on('minute', async () => { } }); - emitter.emit('cancel', session); + emitter.emit('cancel', updatedSession); } else { if (updateWithRatelimit) { if (elapsedSincePause % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); } else { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: false }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); } } else { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); } } @@ -68,7 +70,7 @@ emitter.on('minute', async () => { }); if (elapsed >= session.time) { // TODO: Commit hours to goal, verify hours with events - await prisma.session.update({ + updatedSession = await prisma.session.update({ where: { id: session.id }, From 5efafc9ffb4c4d3fd0a160446da4bcf869fa75a3 Mon Sep 17 00:00:00 2001 From: Lucas Birkert Date: Fri, 9 Aug 2024 19:32:10 +0200 Subject: [PATCH 3/5] Move commplete into corelib --- src/clock.ts | 29 +++-------------------------- src/lib/corelib.ts | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/clock.ts b/src/clock.ts index 24ab7c82..aecdc9c2 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -1,6 +1,7 @@ import { getElapsed, getElapsedSincePaused, prisma } from "./lib/prisma.js"; import { emitter } from "./lib/emitter.js"; import { Constants } from "./lib/constants.js"; +import { Session } from "./lib/corelib.js"; emitter.on('minute', async () => { try { @@ -69,32 +70,8 @@ emitter.on('minute', async () => { } }); - if (elapsed >= session.time) { // TODO: Commit hours to goal, verify hours with events - updatedSession = await prisma.session.update({ - where: { - id: session.id - }, - data: { - completed: true, - elapsed: { - set: session.time - } - } - }); - - // update lifetime minutes - await prisma.user.update({ - where: { - id: session.userId - }, - data: { - lifetimeMinutes: { - increment: session.time - }, - } - }); - - emitter.emit('complete', session); + if (elapsed >= updatedSession.time) { // TODO: Commit hours to goal, verify hours with events + await Session.complete(updatedSession); } else { if (updateWithRatelimit) { if (elapsed % 5 === 0) { diff --git a/src/lib/corelib.ts b/src/lib/corelib.ts index e6705abb..b99c7abd 100644 --- a/src/lib/corelib.ts +++ b/src/lib/corelib.ts @@ -19,6 +19,7 @@ export class Session { * @param {SessionType} session - The session to cancel */ public static async cancel(session: SessionType) { + const elapsed = getElapsed(session); const updatedSession = await prisma.session.update({ where: { id: session.id @@ -26,14 +27,57 @@ export class Session { data: { cancelled: true, elapsed: { - set: getElapsed(session) + set: elapsed } } }); + await prisma.user.update({ + where: { + id: session.userId + }, + data: { + lifetimeMinutes: { + increment: elapsed + }, + } + }); + emitter.emit('cancel', updatedSession); } + /** + * Completes a hack hour session + * @param {SessionType} session - The session to complete + */ + public static async complete(session: SessionType) { + const updatedSession = await prisma.session.update({ + where: { + id: session.id + }, + data: { + cancelled: true, + elapsed: { + set: session.time + } + } + }); + + await prisma.user.update({ + where: { + id: session.userId + }, + data: { + lifetimeMinutes: { + increment: session.time + }, + } + }); + + emitter.emit('complete', updatedSession); + } + + /** * Pauses or resumes a hack hour session * @param {Session} session - The session to pause or resume From adbab49b9d32dcee1c8e168d0ba742bdd5ff5351 Mon Sep 17 00:00:00 2001 From: Lucas Birkert Date: Fri, 9 Aug 2024 19:40:00 +0200 Subject: [PATCH 4/5] Review and fix minor issues --- src/clock.ts | 6 +++--- src/extensions/api/index.ts | 2 +- src/extensions/slack/functions/extend.ts | 2 +- src/extensions/slack/index.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/clock.ts b/src/clock.ts index aecdc9c2..bf635a55 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -75,12 +75,12 @@ emitter.on('minute', async () => { } else { if (updateWithRatelimit) { if (elapsed % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); } else { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: false }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); } } else { - emitter.emit('sessionUpdate', { updatedSession: session, updateSlack: true }); + emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); } } } diff --git a/src/extensions/api/index.ts b/src/extensions/api/index.ts index 24277fcc..1df8074c 100644 --- a/src/extensions/api/index.ts +++ b/src/extensions/api/index.ts @@ -244,7 +244,7 @@ express.get('/api/session/:slackId', readLimit, async (req, res) => { id: slackUser.slackId, createdAt: result.createdAt, time: result.time, - elapsed: getElapsed(result), + elapsed, remaining: result.time - elapsed, endTime: endTime, paused: result.paused, diff --git a/src/extensions/slack/functions/extend.ts b/src/extensions/slack/functions/extend.ts index 538bb07e..4f84ebec 100644 --- a/src/extensions/slack/functions/extend.ts +++ b/src/extensions/slack/functions/extend.ts @@ -59,7 +59,7 @@ Slack.command(Commands.EXTEND, async ({ body }) => { /* const updatedSession = await Session.extend(session, minutes); - informUser(slackId, `Session extended by ${minutes} minutes! Remaining time: ${updatedSession.time-updatedgetElapsed(session)} out of ${updatedSession.time} minutes`, body.channel_id); + informUser(slackId, `Session extended by ${minutes} minutes! Remaining time: ${updatedSession.time-getElapsed(updatedSession)} out of ${updatedSession.time} minutes`, body.channel_id); // Update the session ts await updateController(updatedSession); diff --git a/src/extensions/slack/index.ts b/src/extensions/slack/index.ts index b9508a48..dbf48a6f 100644 --- a/src/extensions/slack/index.ts +++ b/src/extensions/slack/index.ts @@ -502,7 +502,7 @@ emitter.on('sessionUpdate', async (update: { channel: Environment.MAIN_CHANNEL, text: t(`update`, { slackId: slackUser.slackId, - minutes: elapsed + minutes: session.time - elapsed }) }); From f17c41ec20051b14aa9b84a96a8b5da7736dd51a Mon Sep 17 00:00:00 2001 From: Lucas Birkert Date: Fri, 16 Aug 2024 17:33:02 +0200 Subject: [PATCH 5/5] Remove redundant update query and floor elapsed --- src/clock.ts | 45 +++++---------------------------------------- src/lib/prisma.ts | 6 ++++-- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/src/clock.ts b/src/clock.ts index bf635a55..b6139553 100644 --- a/src/clock.ts +++ b/src/clock.ts @@ -30,27 +30,10 @@ emitter.on('minute', async () => { let elapsedSincePause = getElapsedSincePaused(session); if (elapsedSincePause > Constants.AUTO_CANCEL) { - updatedSession = await prisma.session.update({ - where: { - id: session.id - }, - data: { - paused: false, - cancelled: true, - } - }); - - emitter.emit('cancel', updatedSession); + await Session.cancel(session); } else { - if (updateWithRatelimit) { - if (elapsedSincePause % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); - } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); - } - } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); - } + const updateSlack = !updateWithRatelimit || elapsedSincePause % 5 === 0; + emitter.emit('sessionUpdate', { updatedSession, updateSlack }); } continue; @@ -59,29 +42,11 @@ emitter.on('minute', async () => { let elapsed = getElapsed(session); - await prisma.user.update({ - where: { - id: session.userId - }, - data: { - lifetimeMinutes: { - increment: 1 - }, - } - }); - if (elapsed >= updatedSession.time) { // TODO: Commit hours to goal, verify hours with events await Session.complete(updatedSession); } else { - if (updateWithRatelimit) { - if (elapsed % 5 === 0) { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); - } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: false }); - } - } else { - emitter.emit('sessionUpdate', { updatedSession, updateSlack: true }); - } + const updateSlack = !updateWithRatelimit || elapsed % 5 === 0; + emitter.emit('sessionUpdate', { updatedSession, updateSlack }); } } } catch (error) { diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index 3719dabd..192c8439 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -66,7 +66,8 @@ export function getElapsed(session: Session): number { return session.elapsed; } - return Math.min(session.time, session.elapsed + (session.paused ? 0 : (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000)); + const elapsedSince = (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000; + return Math.floor(Math.min(session.time, session.elapsed + (session.paused ? 0 : elapsedSince))); } // This method provides a safe way to get the elapsed pause time from a session. @@ -78,5 +79,6 @@ export function getElapsedSincePaused(session: Session) { return 0; } - return (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000; + const elapsedSince = (Date.now() - session.resumedOrPausedAt.getTime()) / 60_000; + return Math.floor(elapsedSince); }