diff --git a/src/extensions/api/index.ts b/src/extensions/api/index.ts index 6c5d91be..6e8aa990 100644 --- a/src/extensions/api/index.ts +++ b/src/extensions/api/index.ts @@ -359,6 +359,7 @@ express.get('/api/goals/:slackId', readLimit, async (req, res) => { userId: slackUser.userId, }, select: { + id: true, name: true, minutes: true, }, @@ -368,8 +369,9 @@ express.get('/api/goals/:slackId', readLimit, async (req, res) => { ok: true, data: result.map(r => { return { + id: r.id, name: r.name, - minutes: r.minutes + minutes: r.minutes, } }), } @@ -430,6 +432,7 @@ express.get('/api/history/:slackId', readLimit, async (req, res) => { ended: r.completed || r.cancelled, work: r.metadata?.work, + messageTs: r.messageTs, } }) } @@ -437,6 +440,60 @@ express.get('/api/history/:slackId', readLimit, async (req, res) => { return res.status(200).send(response); }); +/** + * Get shop info of a user + */ +express.get('/api/shop/:slackId', readLimit, async (req, res) => { + if (!req.apiKey) { + return res.status(401).send({ + ok: false, + error: 'Unauthorized', + }); + } + + const apiKey = scryptSync(req.apiKey, 'salt', 64).toString('hex'); + + + const user = await prisma.user.findUnique({ + where: { + apiKey + }, + include: { + slackUser: { + select: { + slackId: true + } + } + } + }); + + if (!user || !user.slackUser?.slackId) { + return res.status(401).send({ + ok: false, + error: 'Unauthorized', + }); + } + + const airtableUser = await AirtableAPI.User.lookupBySlack(user.slackUser.slackId); + + if (!airtableUser) { + return res.status(401).send({ + ok: false, + error: 'Unauthorized', + }); + } + + return res.status(200).send({ + ok: true, + data: { + spendable: airtableUser.fields["Balance (Hours)"], + awaitingApproval: airtableUser.fields["Minutes (Pending Approval)"] / 60, + inOrders: airtableUser.fields["In Pending (Minutes)"] / 60, + spent: airtableUser.fields["Spent Fulfilled (Minutes)"] / 60, + } + }) +}) + /* Write API */ @@ -556,8 +613,10 @@ express.post('/api/start/:slackId', limiter, async (req, res) => { } }); - await updateController(session); - await updateTopLevel(session); + await Promise.all([ + updateController(session), + updateTopLevel(session) + ]) emitter.emit('start', session); @@ -573,6 +632,7 @@ express.post('/api/start/:slackId', limiter, async (req, res) => { id: session.id, slackId: user.slackUser?.slackId, createdAt: session.createdAt, + messageTs: assertVal(topLevel!.ts), }, }); }); @@ -634,6 +694,7 @@ express.post('/api/cancel/:slackId', limiter, async (req, res) => { id: session.id, slackId: session.user.slackUser?.slackId, createdAt: session.createdAt, + messageTs: session.messageTs, }, }); } catch (error) { @@ -699,9 +760,87 @@ express.post('/api/pause/:slackId', limiter, async (req, res) => { slackId: session.user.slackUser?.slackId, createdAt: session.createdAt, paused: updatedSession.paused, + messageTs: session.messageTs, + }, + }); + } catch (error) { + emitter.emit('error', { error }); + } +}); + +/** + * Change the goal of an active session + */ +express.post('/api/goals/:slackId', limiter, async (req, res) => { + try { + if (!req.apiKey) { + return res.status(401).send({ + ok: false, + error: 'Unauthorized', + }); + } + + const apiKey = scryptSync(req.apiKey, 'salt', 64).toString('hex'); + + const session = await prisma.session.findFirst({ + where: { + user: { + apiKey + }, + completed: false, + cancelled: false, + }, + include: { + user: { + select: { + slackUser: { + select: { + slackId: true + } + } + } + } + } + }); + + if (!session || !session.user.slackUser?.slackId) { + return res.status(401).send({ + ok: false, + error: 'Invalid user or no active session found', + }); + } + + const result = await prisma.goal.findMany({ + where: { + userId: session.user.slackUser.slackId, + }, + select: { + id: true, + name: true, + minutes: true + }, + }); + + if (typeof req.body.id !== 'string' || !result.find(i => i.id === req.body.id)) { + return res.status(400).send({ + ok: false, + error: "Invalid Goal ID" + }) + } + + await Session.changeGoal(session, req.body.id); + + return res.status(200).send({ + ok: true, + data: { + id: session.id, + slackId: session.user.slackUser?.slackId, + createdAt: session.createdAt, + goal: result.find(i => i.id === req.body.id), + messageTs: session.messageTs, }, }); } catch (error) { emitter.emit('error', { error }); } -}); \ No newline at end of file +}) \ No newline at end of file diff --git a/src/extensions/slack/functions/goals.ts b/src/extensions/slack/functions/goals.ts index 3eb15c21..8bd45ce0 100644 --- a/src/extensions/slack/functions/goals.ts +++ b/src/extensions/slack/functions/goals.ts @@ -7,6 +7,7 @@ import { informUser, updateController, updateTopLevel } from "../lib/lib.js"; import { emitter } from "../../../lib/emitter.js"; import { t } from "../../../lib/templates.js"; import { Loading } from "../views/loading.js"; +import { Session } from "../../../lib/corelib.js"; Slack.action(Actions.OPEN_GOAL, async ({ body, client }) => { try { @@ -74,60 +75,7 @@ Slack.action(Actions.SELECT_GOAL, async ({ body, client }) => { } }); - const oldGoal = await prisma.goal.findUniqueOrThrow({ - where: { - id: session?.goalId as string - } - }); - - const newGoal = await prisma.goal.findUniqueOrThrow({ - where: { - id: goalId - } - }); - - session = await prisma.session.update({ - where: { - id: sessionId, - goal: { - completed: false - }, - }, - data: { - goal: { - connect: { - id: newGoal.id - } - } - } - }); - - if (session.completed || session.cancelled) { - await prisma.goal.update({ - where: { - id: oldGoal.id - }, - data: { - minutes: { - decrement: session.elapsed - } - } - }); - - await prisma.goal.update({ - where: { - id: newGoal.id - }, - data: { - minutes: { - increment: session.elapsed - } - } - }); - } - - updateTopLevel(session); - updateController(session); + session = await Session.changeGoal(session, goalId); await client.views.update({ view_id: (body as any).view.root_view_id, diff --git a/src/lib/corelib.ts b/src/lib/corelib.ts index cbcd786c..93b316c5 100644 --- a/src/lib/corelib.ts +++ b/src/lib/corelib.ts @@ -3,6 +3,7 @@ import type { Session as SessionType } from "@prisma/client"; import { prisma } from "./prisma.js"; import { emitter } from "./emitter.js"; +import { updateController, updateTopLevel } from "../extensions/slack/lib/lib.js"; interface SessionAction { userId?: string; @@ -75,6 +76,67 @@ export class Session { return updatedSession; } + + public static async changeGoal(session: SessionType, goalId: string) { + const oldGoal = await prisma.goal.findUniqueOrThrow({ + where: { + id: session?.goalId as string + } + }); + + const newGoal = await prisma.goal.findUniqueOrThrow({ + where: { + id: goalId + } + }); + + const updatedSession = await prisma.session.update({ + where: { + id: session.id, + goal: { + completed: false + }, + }, + data: { + goal: { + connect: { + id: newGoal.id + } + } + } + }); + + if (session.completed || session.cancelled) { + await prisma.goal.update({ + where: { + id: oldGoal.id + }, + data: { + minutes: { + decrement: session.elapsed + } + } + }); + + await prisma.goal.update({ + where: { + id: newGoal.id + }, + data: { + minutes: { + increment: session.elapsed + } + } + }); + } + + await Promise.all([ + updateController(updatedSession), + updateTopLevel(updatedSession) + ]) + + return updatedSession; + } } // TODO: Metadata management