eletrotupi / tcc/ commit / 92fcd2f

api: store/fetch triggers

Pedro Lucas Porcellis porcellis@eletrotupi.com · 4 months ago 92fcd2f123abee1a1608f8f79f17fc9ca250e673 Browse files
Parents 4ef759a
Changed files
8
Additions
+174
Deletions
-2
/dev/null → api/prisma/migrations/20260604225204_rename_triggers_enums/migration.sql
1 +
/*
2 +
  Warnings:
3 +

4 +
  - The values [ENVIRONMENTAL,EMOTIONAL] on the enum `TriggerType` will be removed. If these variants are still used in the database, this will fail.
5 +

6 +
*/
7 +
-- AlterEnum
8 +
BEGIN;
9 +
CREATE TYPE "TriggerType_new" AS ENUM ('SOCIAL', 'WORK', 'HEALTH', 'PHYSICAL', 'FAMILY', 'OTHER');
10 +
ALTER TABLE "triggers" ALTER COLUMN "category" TYPE "TriggerType_new" USING ("category"::text::"TriggerType_new");
11 +
ALTER TYPE "TriggerType" RENAME TO "TriggerType_old";
12 +
ALTER TYPE "TriggerType_new" RENAME TO "TriggerType";
13 +
DROP TYPE "public"."TriggerType_old";
14 +
COMMIT;
api/prisma/schema.prisma
1 1
generator client {
2 2
  provider = "prisma-client-js"
3 3
  //output   = "../src/generated/prisma"
4 4
}
5 5

125 125
  CONVERSATION
126 126
}
127 127

128 128
enum TriggerType {
129 129
  SOCIAL
130 -
  ENVIRONMENTAL
131 -
  EMOTIONAL
130 +
  WORK
131 +
  HEALTH
132 132
  PHYSICAL
133 +
  FAMILY
133 134
  OTHER
134 135
}
135 136

136 137
// XXX: Keep it in sync with constants/moods.ts
137 138
enum BaseMoodOption {
150 151
  ANXIETY
151 152
  GRATITUDE
152 153
  FOCUS
153 154
  TIREDNESS
154 155
}
/dev/null → api/src/controllers/triggers.ts
1 +
import { Request, Response, NextFunction } from 'express';
2 +
import { AuthenticatedRequest } from '@app/middleware/auth';
3 +
import {
4 +
  createTrigger,
5 +
  getTriggersByUserId,
6 +
  destroyTriggerById
7 +
} from '@app/services/trigger.service';
8 +

9 +
import {
10 +
  CreateTriggerSchema
11 +
} from '@app/schemas'
12 +

13 +
export const TriggersController = {
14 +
  create: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
15 +
    try {
16 +
      const parsed = CreateTriggerSchema.safeParse(req.body.trigger);
17 +

18 +
      if (!parsed.success) {
19 +
        return res.status(400).json({
20 +
          errors: parsed.error!.issues
21 +
        });
22 +
      }
23 +

24 +
      const trigger = await createTrigger(req.userId!, parsed.data);
25 +

26 +
      return res.status(201).json({ trigger });
27 +
    } catch (err) {
28 +
      next(err);
29 +
    }
30 +
  },
31 +

32 +
  index: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
33 +
    try {
34 +
      const result = await getTriggersByUserId(Number(req.userId), {
35 +
        limit: req.query.limit ? Number(req.query.limit) : undefined,
36 +
        page:  req.query.page  ? Number(req.query.page)  : undefined,
37 +
        from:  req.query.from  as string | undefined,
38 +
        to:    req.query.to    as string | undefined,
39 +
      });
40 +

41 +
      return res.status(200).json(
42 +
        result
43 +
      )
44 +
    } catch (err) {
45 +
      next(err);
46 +
    }
47 +
  },
48 +

49 +
  destroy: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
50 +
    try {
51 +
      const result = await destroyTriggerById(req.userId!, Number(req.params.id!));
52 +

53 +
      return res.status(200).json({})
54 +
    } catch (err) {
55 +
      next(err);
56 +
    }
57 +
  }
58 +
};
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';
6 6
import userRouter from '@app/routes/users';
7 7
import authRouter from '@app/routes/auth';
8 8
import moodRouter from '@app/routes/moods';
9 +
import triggerRouter from '@app/routes/triggers';
9 10
import sleepRecordRouter from '@app/routes/sleepRecords';
10 11
import { errorHandler } from '@app/middleware/errorHandler';
11 12
import { PrismaClient } from '@prisma/client';
12 13
import { bootWorkers, closeAllWorkers, closeAllQueues } from '@app/lib/queue';
13 14
import pino from 'pino';
44 45

45 46
  app.use("/users", userRouter);
46 47
  app.use("/auth", authRouter);
47 48
  app.use("/moods", moodRouter);
48 49
  app.use("/sleep_records", sleepRecordRouter);
50 +
  app.use("/triggers", triggerRouter);
49 51

50 52
  app.use(errorHandler); // always last
51 53

52 54
  // TODO: flesh the actual page here
53 55
  app.use(express.static(path.join(__dirname, './static')));
62 64

63 65
const shutdown = async () => {
64 66
  await Promise.all([closeAllWorkers(), closeAllQueues()]);
65 67
  process.exit(0);
66 68
};
/dev/null → api/src/routes/triggers.ts
1 +
import { Router } from "express";
2 +
import { TriggersController } from '@app/controllers/triggers'
3 +
import { requireAuth } from '@app/middleware/auth';
4 +

5 +
const router = Router();
6 +

7 +
router.get('/', requireAuth, TriggersController.index);
8 +
router.post('/', requireAuth, TriggersController.create);
9 +
router.delete('/:id', requireAuth, TriggersController.destroy);
10 +

11 +
export default router;
api/src/schemas/index.ts
1 1
export {
2 2
  CreateMoodSchema,
3 3
  MoodComponentSchema
4 4
} from '@app/schemas/mood.schema';
5 5

27 27

28 28
export {
29 29
  type CreateSleepRecordInput,
30 30
  CreateSleepRecordSchema
31 31
} from '@app/schemas/sleepRecord.schema';
32 +

33 +
export {
34 +
  type CreateTriggerInput,
35 +
  CreateTriggerSchema
36 +
} from '@app/schemas/trigger.schema';
/dev/null → api/src/schemas/trigger.schema.ts
1 +
import { z } from 'zod';
2 +
import {
3 +
  TriggerType
4 +
} from '@prisma/client';
5 +

6 +
export const CreateTriggerSchema = z.object({
7 +
  comment: z.string().optional(),
8 +
  moment: z.coerce.date(),
9 +
  category: z.nativeEnum(TriggerType)
10 +
});
11 +

12 +
export type CreateTriggerInput = z.infer<typeof CreateTriggerSchema>;
/dev/null → api/src/services/trigger.service.ts
1 +
import { prisma } from '@app/lib/prisma';
2 +
import { Trigger } from '@prisma/client';
3 +

4 +
import { CreateTriggerInput } from '@app/schemas';
5 +

6 +
export const createTrigger = async (
7 +
  userId: number, input: CreateTriggerInput
8 +
): Promise<Trigger> => {
9 +
  return prisma.trigger.create({
10 +
    data: {
11 +
      ...input,
12 +
      userId
13 +
    }
14 +
  });
15 +
};
16 +

17 +
export const getTriggersByUserId = async (
18 +
  userId: number,
19 +
  params?: {
20 +
    limit?: number;
21 +
    page?: number;
22 +
    from?: string;
23 +
    to?: string;
24 +
  }
25 +
): Promise<{ entries: Trigger[]; total: number; page: number; nextPage: number | null }> => {
26 +
  const page  = params?.page  ?? 1;
27 +
  const limit = params?.limit ?? 20;
28 +
  const skip  = (page - 1) * limit;
29 +

30 +
  const where = {
31 +
    userId,
32 +
    ...(params?.from || params?.to ? {
33 +
      moment: {
34 +
        ...(params.from && { gte: new Date(params.from) }),
35 +
        ...(params.to   && { lte: new Date(params.to)   }),
36 +
      }
37 +
    } : {}),
38 +
  };
39 +

40 +
  const [entries, total] = await Promise.all([
41 +
    prisma.trigger.findMany({
42 +
      where,
43 +
      orderBy: { moment: 'desc' },
44 +
      take: limit,
45 +
      skip,
46 +
    }),
47 +
    prisma.trigger.count({ where }),
48 +
  ]);
49 +

50 +
  const totalPages = Math.ceil(total / limit);
51 +

52 +
  return {
53 +
    entries,
54 +
    total,
55 +
    page,
56 +
    nextPage: page < totalPages ? page + 1 : null,
57 +
  };
58 +
};
59 +

60 +
export const destroyTriggerById = async (
61 +
  userId: number, id: number
62 +
): Promise<Trigger> => {
63 +
  return await prisma.trigger.delete({
64 +
    where: {
65 +
      id,
66 +
      userId
67 +
    }
68 +
  })
69 +
}