eletrotupi / tcc/ commit / 729fa78

api: link trigger and moods

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago 729fa78803011f9075814181562b4f7ea4bb9974
Parents: 1b7bbac
9 file(s) changed
  • api/src/controllers/triggerMoodLinks.ts +41 -0
  • api/src/controllers/triggers.ts +39 -2
  • api/src/createApp.ts +4 -0
  • api/src/lib/queue/processors/insights/triggerPattern.ts +2 -0
  • api/src/routes/triggers.ts +2 -0
  • api/src/schemas/triggerMoodLink.schema.ts +8 -0
  • api/src/services/mood.service.ts +8 -2
  • api/src/services/trigger.service.ts +9 -3
  • api/src/services/triggerMoodLink.service.ts +33 -0
api/src/controllers/triggerMoodLinks.ts
@@ -0,0 +1,41 @@
1 + import { Response, NextFunction } from 'express';
2 + import { AuthenticatedRequest } from '@app/middleware/auth';
3 + import { LinkMoodSchema } from '@app/schemas';
4 + import {
5 + linkTriggerToMood,
6 + unlinkTriggerFromMood,
7 + } from '@app/services/triggerMoodLink.service';
8 +
9 + export const TriggerMoodLinksController = {
10 + linkMood: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
11 + try {
12 + const parsed = LinkMoodSchema.safeParse(req.body);
13 +
14 + if (!parsed.success) {
15 + return res.status(400).json({ errors: parsed.error!.issues });
16 + }
17 +
18 + const triggerId = Number(req.params.triggerId);
19 + const { moodId, perceivedImpact } = parsed.data;
20 +
21 + const link = await linkTriggerToMood(triggerId, moodId, perceivedImpact);
22 +
23 + return res.status(201).json({ link });
24 + } catch (err) {
25 + next(err);
26 + }
27 + },
28 +
29 + unlinkMood: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
30 + try {
31 + const triggerId = Number(req.params.triggerId);
32 + const moodId = Number(req.params.moodId);
33 +
34 + await unlinkTriggerFromMood(triggerId, moodId);
35 +
36 + return res.status(200).json({});
37 + } catch (err) {
38 + next(err);
39 + }
40 + },
41 + };
api/src/controllers/triggers.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
import { AuthenticatedRequest } from '@app/middleware/auth';
3 3
import {
4 4
  createTrigger,
5 5
  getTriggersByUserId,
6 6
  destroyTriggerById,
7 7
  getTriggerById,
8 8
  updateTriggerById
9 9
} from '@app/services/trigger.service';
10 +
import {
11 +
  linkTriggerToMood,
12 +
  unlinkTriggerFromMood,
13 +
} from '@app/services/triggerMoodLink.service';
10 14

11 15
import {
12 16
  CreateTriggerSchema,
13 -
  UpdateTriggerSchema
17 +
  UpdateTriggerSchema,
18 +
  LinkMoodSchema,
14 19
} from '@app/schemas'
15 20

16 21
export const TriggersController = {
17 22
  create: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
18 23
    try {
▸ 64 unchanged lines
83 88

84 89
      return res.status(200).json({ trigger });
85 90
    } catch (err) {
86 91
      next(err);
87 92
    }
88 -
  }
93 +
  },
94 +

95 +
  linkMood: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
96 +
    try {
97 +
      const parsed = LinkMoodSchema.safeParse(req.body);
98 +

99 +
      if (!parsed.success) {
100 +
        return res.status(400).json({ errors: parsed.error!.issues });
101 +
      }
102 +

103 +
      const triggerId = Number(req.params.triggerId);
104 +
      const { moodId, perceivedImpact } = parsed.data;
105 +

106 +
      const link = await linkTriggerToMood(triggerId, moodId, perceivedImpact);
107 +

108 +
      return res.status(201).json({ link });
109 +
    } catch (err) {
110 +
      next(err);
111 +
    }
112 +
  },
113 +

114 +
  unlinkMood: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
115 +
    try {
116 +
      const triggerId = Number(req.params.triggerId);
117 +
      const moodId = Number(req.params.moodId);
118 +

119 +
      await unlinkTriggerFromMood(triggerId, moodId);
120 +

121 +
      return res.status(200).json({});
122 +
    } catch (err) {
123 +
      next(err);
124 +
    }
125 +
  },
89 126
};
api/src/createApp.ts
1 1
import express from "express";
2 2
import cors from "cors";
3 3
import bodyParser from "body-parser";
4 4
import morgan from "morgan";
5 5
import mainRouter from "@app/routes/main";
▸ 1 unchanged lines
7 7
import authRouter from "@app/routes/auth";
8 8
import moodRouter from "@app/routes/moods";
9 9
import triggerRouter from "@app/routes/triggers";
10 10
import sleepRecordRouter from "@app/routes/sleepRecords";
11 11
import insightRouter from "@app/routes/insights";
12 +
import careActionsRouter from "@app/routes/careActions";
13 +
import medicineRegimensRouter from "@app/routes/medicineRegimens";
12 14
import { errorHandler } from "@app/middleware/errorHandler";
13 15
import { PrismaClient } from "@prisma/client";
14 16
import {
15 17
  bootWorkers,
16 18
  closeAllWorkers,
▸ 43 unchanged lines
60 62
  app.use("/auth", authRouter);
61 63
  app.use("/moods", moodRouter);
62 64
  app.use("/sleep_records", sleepRecordRouter);
63 65
  app.use("/triggers", triggerRouter);
64 66
  app.use("/insights", insightRouter);
67 +
  app.use("/care-actions", careActionsRouter);
68 +
  app.use("/medicine-regimens", medicineRegimensRouter);
65 69

66 70
  app.use(errorHandler); // always last
67 71

68 72
  // TODO: flesh the actual page here
69 73
  app.use(express.static(path.join(__dirname, "./static")));
▸ 9 unchanged lines
79 83

80 84
const shutdown = async () => {
81 85
  await Promise.all([closeAllWorkers(), closeAllQueues()]);
82 86
  process.exit(0);
83 87
};
api/src/lib/queue/processors/insights/triggerPattern.ts
1 1
import { prisma } from "@app/lib/prisma";
2 2
import { InsightType, InsightPeriod, TriggerType } from "@prisma/client";
3 3
import { InsightPeriodPayload } from "@app/lib/queue/types";
4 4

5 5
const TRIGGER_LABEL: Record<TriggerType, string> = {
6 6
  SOCIAL: "social",
7 7
  WORK: "trabalho",
8 8
  HEALTH: "saúde",
9 9
  PHYSICAL: "físico",
10 10
  FAMILY: "família",
11 +
  THERAPY: "terapia",
12 +
  INTERNAL: "interno",
11 13
  OTHER: "outros",
12 14
};
13 15

14 16
export async function processTriggerPattern({
15 17
  userId,
▸ 76 unchanged lines
92 94
  `
93 95
  }
94 96

95 97
  return template.replace(/\s*\n\s*/g, " ").trim();
96 98
}
api/src/routes/triggers.ts
1 1
import { Router } from "express";
2 2
import { TriggersController } from '@app/controllers/triggers'
3 3
import { requireAuth } from '@app/middleware/auth';
4 4

5 5
const router = Router();
▸ 1 unchanged lines
7 7
router.get('/', requireAuth, TriggersController.index);
8 8
router.get('/:id', requireAuth, TriggersController.show);
9 9
router.post('/', requireAuth, TriggersController.create);
10 10
router.put('/:id', requireAuth, TriggersController.update);
11 11
router.delete('/:id', requireAuth, TriggersController.destroy);
12 +
router.post('/:triggerId/link-mood', requireAuth, TriggersController.linkMood);
13 +
router.delete('/:triggerId/link-mood/:moodId', requireAuth, TriggersController.unlinkMood);
12 14

13 15
export default router;
api/src/schemas/triggerMoodLink.schema.ts
@@ -0,0 +1,8 @@
1 + import { z } from 'zod';
2 +
3 + export const LinkMoodSchema = z.object({
4 + moodId: z.number().int().positive(),
5 + perceivedImpact: z.number().int().min(1).max(5),
6 + });
7 +
8 + export type LinkMoodInput = z.infer<typeof LinkMoodSchema>;
api/src/services/mood.service.ts
1 1
import { prisma } from "@app/lib/prisma";
2 2
import { Mood } from "@prisma/client";
3 3
import { rollingPeriod } from "@app/lib/queue/processors/insights/utils";
4 4
import { InsightJobName } from "@app/lib/queue/types";
5 5
import { getQueue } from "@app/lib/queue";
▸ 71 unchanged lines
77 77
    page,
78 78
    nextPage: page < totalPages ? page + 1 : null,
79 79
  };
80 80
};
81 81

82 -
export const getMoodById = async (id: number): Promise<Mood | null> => {
82 +
export const getMoodById = async (id: number) => {
83 83
  return await prisma.mood.findUnique({
84 84
    where: {
85 85
      id,
86 86
    },
87 -
    include: { moodComponents: true },
87 +
    include: {
88 +
      moodComponents: true,
89 +
      triggerLinks: {
90 +
        include: { trigger: true },
91 +
        orderBy: { linkedAt: 'desc' },
92 +
      },
93 +
    },
88 94
  });
89 95
};
90 96

91 97
export const destroyMoodById = async (
92 98
  userId: number,
▸ 42 unchanged lines
135 141
        removeOnFail: true,
136 142
      },
137 143
    },
138 144
  ]);
139 145
};
api/src/services/trigger.service.ts
1 1
import { prisma } from '@app/lib/prisma';
2 2
import { Trigger } from '@prisma/client';
3 3

4 4
import { CreateTriggerInput, UpdateTriggerInput } from '@app/schemas';
5 5
import { rollingPeriod } from "@app/lib/queue/processors/insights/utils";
▸ 71 unchanged lines
77 77
  void enqueueTriggerPatternInsight(userId);
78 78

79 79
  return trigger
80 80
}
81 81

82 -
export const getTriggerById = async (userId: number, id: number): Promise<Trigger | null> => {
82 +
export const getTriggerById = async (userId: number, id: number) => {
83 83
  return await prisma.trigger.findUnique({
84 -
    where: { userId, id }
85 -
  })
84 +
    where: { userId, id },
85 +
    include: {
86 +
      moodLinks: {
87 +
        include: { mood: { include: { moodComponents: true } } },
88 +
        orderBy: { linkedAt: 'desc' },
89 +
      },
90 +
    },
91 +
  });
86 92
}
87 93

88 94
export const updateTriggerById = async (
89 95
  userId: number, id: number, input: UpdateTriggerInput
90 96
): Promise<Trigger> => {
▸ 19 unchanged lines
110 116
      removeOnComplete: true,
111 117
      removeOnFail: true,
112 118
    }
113 119
  );
114 120
}
api/src/services/triggerMoodLink.service.ts
@@ -0,0 +1,33 @@
1 + import { prisma } from '@app/lib/prisma';
2 + import { TriggerMoodLink } from '@prisma/client';
3 +
4 + export const linkTriggerToMood = async (
5 + triggerId: number,
6 + moodId: number,
7 + perceivedImpact: number,
8 + ): Promise<TriggerMoodLink> => {
9 + const existing = await prisma.triggerMoodLink.findUnique({
10 + where: {
11 + triggerId_moodId: { triggerId, moodId },
12 + },
13 + });
14 +
15 + if (existing) {
16 + throw new Error(`Link between trigger ${triggerId} and mood ${moodId} already exists`);
17 + }
18 +
19 + return prisma.triggerMoodLink.create({
20 + data: { triggerId, moodId, perceivedImpact },
21 + });
22 + };
23 +
24 + export const unlinkTriggerFromMood = async (
25 + triggerId: number,
26 + moodId: number,
27 + ): Promise<TriggerMoodLink> => {
28 + return prisma.triggerMoodLink.delete({
29 + where: {
30 + triggerId_moodId: { triggerId, moodId },
31 + },
32 + });
33 + };