api: add a controller for fetching insights

Pedro Lucas Porcellis porcellis@eletrotupi.com 4 days 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
@@ -28,24 +28,26 @@ 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 }),
@@ -59,16 +61,18 @@ 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 }
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,18 +1,16 @@
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,
@@ -22,15 +20,20 @@ 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 + };