eletrotupi / tcc/ commit / b3cae39

api: add a controller for fetching insights

Pedro Lucas Porcellis porcellis@eletrotupi.com 1 month ago b3cae39fbd7a790d651c1950d4bc42dc3bc9e9aa
Parents: a772f94
6 file(s) changed
  • api/src/controllers/insights.ts +33 -0
  • api/src/createApp.ts +16 -12
  • api/src/routes/insights.ts +9 -0
  • api/src/schemas/index.ts +15 -12
  • api/src/schemas/insight.schema.ts +10 -0
  • api/src/services/insight.service.ts +31 -0
api/src/controllers/insights.ts
@@ -0,0 +1,33 @@
1 + import { Response, NextFunction } from "express";
2 + import { AuthenticatedRequest } from "@app/middleware/auth";
3 + import { getInsightsByUserId } from "@app/services/insight.service";
4 + import { InsightsQuerySchema } from "@app/schemas";
5 +
6 + export const InsightsController = {
7 + index: async (
8 + req: AuthenticatedRequest,
9 + res: Response,
10 + next: NextFunction,
11 + ) => {
12 + try {
13 + const parsedQuery = InsightsQuerySchema.safeParse(req.query);
14 +
15 + if (!parsedQuery.success) {
16 + return res.status(400).json({
17 + message: "Invalid query parameters",
18 + errors: parsedQuery.error.flatten(),
19 + });
20 + }
21 +
22 + const insights = await getInsightsByUserId(req.userId!, {
23 + type: parsedQuery.data.type,
24 + period: parsedQuery.data.period,
25 + limit: parsedQuery.data.limit,
26 + });
27 +
28 + return res.status(200).json({ insights });
29 + } catch (err) {
30 + next(err);
31 + }
32 + },
33 + };
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";
▸ 20 unchanged lines
26 26
  const app = express();
27 27
  bootWorkers();
28 28
  scheduleInsights();
29 29

30 30
  // Configure CORS
31 -
  app.use(cors({
32 -
    origin: process.env.FRONTEND_URL || '*',
33 -
    credentials: true
34 -
  }));
31 +
  app.use(
32 +
    cors({
33 +
      origin: process.env.FRONTEND_URL || "*",
34 +
      credentials: true,
35 +
    }),
36 +
  );
35 37

36 38
  app.use(bodyParser.json());
37 39

38 -
  const isDev = process.env.NODE_ENV !== 'production';
40 +
  const isDev = process.env.NODE_ENV !== "production";
39 41

40 42
  const logger = pino({
41 -
    level: process.env.LOG_LEVEL || 'info',
43 +
    level: process.env.LOG_LEVEL || "info",
42 44
    ...(isDev && {
43 45
      transport: {
44 -
        target: 'pino-pretty',
46 +
        target: "pino-pretty",
45 47
        options: {
46 48
          colorize: true,
47 49
          singleLine: false,
48 -
          messageFormat: '{if levelLabel}{levelLabel} - {end}{msg}',
50 +
          messageFormat: "{if levelLabel}{levelLabel} - {end}{msg}",
49 51
        },
50 52
      },
51 53
    }),
52 54
  });
53 55

▸ 3 unchanged lines
57 59
  app.use("/users", userRouter);
58 60
  app.use("/auth", authRouter);
59 61
  app.use("/moods", moodRouter);
60 62
  app.use("/sleep_records", sleepRecordRouter);
61 63
  app.use("/triggers", triggerRouter);
64 +
  app.use("/insights", insightRouter);
62 65

63 66
  app.use(errorHandler); // always last
64 67

65 68
  // TODO: flesh the actual page here
66 -
  app.use(express.static(path.join(__dirname, './static')));
69 +
  app.use(express.static(path.join(__dirname, "./static")));
67 70
  app.get(/.*/, (req, res) => {
68 -
    res.sendFile(path.join(__dirname, './static/index.html')); });
71 +
    res.sendFile(path.join(__dirname, "./static/index.html"));
72 +
  });
69 73

70 -
  process.on('SIGTERM', shutdown);
71 -
  process.on('SIGINT', shutdown);
74 +
  process.on("SIGTERM", shutdown);
75 +
  process.on("SIGINT", shutdown);
72 76

73 77
  return app;
74 78
}
75 79

76 80
const shutdown = async () => {
77 81
  await Promise.all([closeAllWorkers(), closeAllQueues()]);
78 82
  process.exit(0);
79 83
};
api/src/routes/insights.ts
@@ -0,0 +1,9 @@
1 + import { Router } from "express";
2 + import { requireAuth } from "@app/middleware/auth";
3 + import { InsightsController } from "@app/controllers/insights";
4 +
5 + const router = Router();
6 +
7 + router.get("/", requireAuth, InsightsController.index);
8 +
9 + export default router;
api/src/schemas/index.ts
1 1
export {
2 2
  CreateMoodSchema,
3 -
  MoodComponentSchema
4 -
} from '@app/schemas/mood.schema';
3 +
  MoodComponentSchema,
4 +
} from "@app/schemas/mood.schema";
5 5

6 -
export type {
7 -
  CreateMoodInput
8 -
} from '@app/schemas/mood.schema';
6 +
export type { CreateMoodInput } from "@app/schemas/mood.schema";
9 7

10 8
export {
11 9
  CreateUserSchema,
12 10
  UpdateUserSchema,
13 11
  type CreateUserInput,
14 12
  type UpdateUserInput,
15 -
} from '@app/schemas/user.schema';
13 +
} from "@app/schemas/user.schema";
16 14

17 15
export {
18 16
  LoginSchema,
19 17
  PasswordResetRequestSchema,
20 18
  PasswordResetSchema,
21 19
  ActivateUserSchema,
22 20
  type LoginInput,
23 21
  type PasswordResetRequestInput,
24 22
  type PasswordResetInput,
25 -
  type ActivateUserInput
26 -
} from '@app/schemas/auth.schema';
23 +
  type ActivateUserInput,
24 +
} from "@app/schemas/auth.schema";
27 25

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

33 31
export {
34 32
  type CreateTriggerInput,
35 -
  CreateTriggerSchema
36 -
} from '@app/schemas/trigger.schema';
33 +
  CreateTriggerSchema,
34 +
} from "@app/schemas/trigger.schema";
35 +

36 +
export {
37 +
  InsightsQuerySchema,
38 +
  type InsightsQueryInput,
39 +
} from "@app/schemas/insight.schema";
api/src/schemas/insight.schema.ts
@@ -0,0 +1,10 @@
1 + import { z } from 'zod';
2 + import { InsightPeriod, InsightType } from '@prisma/client';
3 +
4 + export const InsightsQuerySchema = z.object({
5 + type: z.nativeEnum(InsightType).optional(),
6 + period: z.nativeEnum(InsightPeriod).optional(),
7 + limit: z.coerce.number().int().positive().max(100).optional(),
8 + });
9 +
10 + export type InsightsQueryInput = z.infer<typeof InsightsQuerySchema>;
api/src/services/insight.service.ts
@@ -0,0 +1,31 @@
1 + import { prisma } from "@app/lib/prisma";
2 + import { Insight, InsightType, InsightPeriod } from "@prisma/client";
3 +
4 + export const getInsightsByUserId = async (
5 + userId: number,
6 + params?: {
7 + type?: InsightType;
8 + period?: InsightPeriod;
9 + limit?: number;
10 + },
11 + ): Promise<Insight[]> => {
12 + return prisma.insight.findMany({
13 + where: {
14 + userId,
15 + ...(params?.type && { type: params.type }),
16 + ...(params?.period && { period: params.period }),
17 + },
18 + orderBy: { generatedAt: "desc" },
19 + take: params?.limit ?? 10,
20 + });
21 + };
22 +
23 + export const getLatestInsightByType = async (
24 + userId: number,
25 + type: InsightType,
26 + ): Promise<Insight | null> => {
27 + return prisma.insight.findFirst({
28 + where: { userId, type },
29 + orderBy: { generatedAt: "desc" },
30 + });
31 + };