api: add full blown mood endpoint

Holy shit, this was a wild ride. I've refactored to address a couple of
bunch of lying issues:

- No auth middleware being used
- Weird ass models that weren't models at all
- Basically ignoring types everywhere
- Weird shape of parameters being tossed around
- Controllers doing validations + models doing validations

These need to be ported now to the auth and user endpoints as well. Then
it'll probably be easier to just keep it tidy.

Now, Prisma can keep working as our repository layer, our services actually
behave as services instead of "models", validation happens with Zod at
the controller level, and schemas are there to keep track of what needs
to be validated. A problem is that now it lacks good i18n translation,
so we gotta patch that sometime
Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago 4831e19b7a0b1f141e9b8ba7372f931fb909d5f4
Parents: bc4e3ca
6 file(s) changed
  • api/src/controllers/moods.ts +42 -0
  • api/src/createApp.ts +2 -0
  • api/src/routes/moods.ts +10 -0
  • api/src/schemas/index.ts +8 -0
  • api/src/schemas/mood.schema.ts +23 -0
  • api/src/services/mood.service.ts +27 -0
api/src/controllers/moods.ts
@@ -0,0 +1,42 @@
1 + import { Request, Response, NextFunction } from 'express';
2 + import { AuthenticatedRequest } from '@app/middleware/auth';
3 + import {
4 + createMood,
5 + getMoodsByUserId
6 + } from '@app/services/mood.service';
7 +
8 + import {
9 + CreateMoodSchema
10 + } from '@app/schemas'
11 +
12 + export const MoodsController = {
13 + create: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
14 + try {
15 + const parsed = CreateMoodSchema.safeParse(req.body.mood);
16 +
17 + if (!parsed.success) {
18 + return res.status(400).json({
19 + errors: parsed.error!.issues
20 + });
21 + }
22 +
23 + const mood = await createMood(req.userId!, parsed.data);
24 +
25 + return res.status(201).json({ mood });
26 + } catch (err) {
27 + next(err);
28 + }
29 + },
30 +
31 + index: async (req: Request, res: Response, next: NextFunction) => {
32 + try {
33 + const moods = await getMoodsByUserId(Number(req.params.id));
34 +
35 + return res.status(200).json({
36 + moods
37 + })
38 + } catch (err) {
39 + next(err);
40 + }
41 + },
42 + };
api/src/createApp.ts
@@ -5,6 +5,7 @@ import morgan from 'morgan';
5 5 import mainRouter from '@app/routes/main';
6 6 import userRouter from '@app/routes/users';
7 7 import authRouter from '@app/routes/auth';
8 + import moodRouter from '@app/routes/moods';
8 9 import { errorHandler } from '@app/middleware/errorHandler';
9 10 import { PrismaClient } from '@prisma/client';
10 11 import { bootWorkers, closeAllWorkers, closeAllQueues } from '@app/lib/queue';
@@ -29,6 +30,7 @@
29 30 app.get('/', mainRouter);
30 31 app.use("/users", userRouter);
31 32 app.use("/auth", authRouter);
33 + app.use("/moods", moodRouter);
32 34
33 35 app.use(errorHandler); // always last
34 36
api/src/routes/moods.ts
@@ -0,0 +1,10 @@
1 + import { Router } from "express";
2 + import { MoodsController } from '@app/controllers/moods'
3 + import { requireAuth } from '@app/middleware/auth';
4 +
5 + const router = Router();
6 +
7 + router.get('/', MoodsController.index);
8 + router.post('/', requireAuth, MoodsController.create);
9 +
10 + export default router;
api/src/schemas/index.ts
@@ -0,0 +1,8 @@
1 + export {
2 + CreateMoodSchema,
3 + MoodComponentSchema
4 + } from '@app/schemas/mood.schema';
5 +
6 + export type {
7 + CreateMoodInput
8 + } from '@app/schemas/mood.schema';
api/src/schemas/mood.schema.ts
@@ -0,0 +1,23 @@
1 + import { z } from 'zod';
2 + import {
3 + BaseMoodOption,
4 + MoodComponentOption,
5 + IntensityLevel
6 + } from '@prisma/client';
7 +
8 + export const MoodComponentSchema = z.object({
9 + component: z.nativeEnum(MoodComponentOption),
10 + intensity: z.nativeEnum(IntensityLevel)
11 + });
12 +
13 + export const CreateMoodSchema = z.object({
14 + annotation: z.string().optional(),
15 + moment: z.coerce.date(),
16 + selectedMood: z.nativeEnum(BaseMoodOption),
17 + anxietyLevel: z.number().int().min(0).max(10),
18 + stressLevel: z.number().int().min(0).max(10),
19 + energyLevel: z.number().int().min(0).max(10),
20 + moodComponents: z.array(MoodComponentSchema).min(1)
21 + });
22 +
23 + export type CreateMoodInput = z.infer<typeof CreateMoodSchema>;
api/src/services/mood.service.ts
@@ -0,0 +1,27 @@
1 + import { prisma } from '@app/lib/prisma';
2 + import { Mood } from '@prisma/client';
3 +
4 + import { CreateMoodInput } from '@app/schemas';
5 +
6 + export const createMood = async (userId: number, input: CreateMoodInput): Promise<Mood> => {
7 + const { moodComponents, ...moodData } = input;
8 +
9 + return prisma.mood.create({
10 + data: {
11 + ...moodData,
12 + userId,
13 + moodComponents: {
14 + create: moodComponents
15 + }
16 + },
17 + include: { moodComponents: true }
18 + });
19 + };
20 +
21 + export const getMoodsByUserId = async (userId: number): Promise<Mood[]> => {
22 + return prisma.mood.findMany({
23 + where: { userId },
24 + include: { moodComponents: true },
25 + orderBy: { moment: 'desc' }
26 + });
27 + };