api: flesh sleep records endpoints
Parents:
4ead6517 file(s) changed
- api/src/controllers/main.ts +1 -1
- api/src/controllers/sleepRecords.ts +58 -0
- api/src/createApp.ts +8 -1
- api/src/routes/sleepRecords.ts +11 -0
- api/src/schemas/index.ts +5 -0
- api/src/schemas/sleepRecord.schema.ts +9 -0
- api/src/services/sleepRecord.service.ts +69 -0
api/src/controllers/main.ts
@@ -5,7 +5,7 @@ // TODO: replace with a HTML response for the main app
5 5 index: async (req: Request, res: Response, next: NextFunction) => {
6 6 try {
7 7 const { message } = req.params;
8 - res.json({ status: 'alive' });
8 + res.send('../static/index.html');
9 9 } catch (err) {
10 10 next(err);
11 11 }
api/src/controllers/sleepRecords.ts
@@ -0,0 +1,58 @@
1 + import { Request, Response, NextFunction } from 'express';
2 + import { AuthenticatedRequest } from '@app/middleware/auth';
3 + import {
4 + createSleepRecord,
5 + getSleepRecordsByUserId,
6 + destroySleepRecordById
7 + } from '@app/services/sleepRecord.service';
8 +
9 + import {
10 + CreateSleepRecordSchema
11 + } from '@app/schemas'
12 +
13 + export const SleepRecordsController = {
14 + create: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
15 + try {
16 + const parsed = CreateSleepRecordSchema.safeParse(req.body.sleepRecord);
17 +
18 + if (!parsed.success) {
19 + return res.status(400).json({
20 + errors: parsed.error!.issues
21 + });
22 + }
23 +
24 + const sleepRecord = await createSleepRecord(req.userId!, parsed.data);
25 +
26 + return res.status(201).json({ sleepRecord });
27 + } catch (err) {
28 + next(err);
29 + }
30 + },
31 +
32 + index: async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
33 + try {
34 + const result = await getSleepRecordsByUserId(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 destroySleepRecordById(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
@@ -6,11 +6,13 @@ 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 sleepRecordRouter from '@app/routes/sleepRecords';
9 10 import { errorHandler } from '@app/middleware/errorHandler';
10 11 import { PrismaClient } from '@prisma/client';
11 12 import { bootWorkers, closeAllWorkers, closeAllQueues } from '@app/lib/queue';
12 13 import pino from 'pino';
13 14 import pinoHttp from 'pino-http';
15 + import path from 'path';
14 16
15 17 export function createApp() {
16 18 const app = express();
@@ -40,12 +42,17 @@
40 42 // Use pino-http for HTTP logging (better than Morgan for structured logs)
41 43 app.use(pinoHttp({ logger }));
42 44
43 - app.get('/', mainRouter);
44 45 app.use("/users", userRouter);
45 46 app.use("/auth", authRouter);
46 47 app.use("/moods", moodRouter);
48 + app.use("/sleep_records", sleepRecordRouter);
47 49
48 50 app.use(errorHandler); // always last
51 +
52 + // TODO: flesh the actual page here
53 + app.use(express.static(path.join(__dirname, './static')));
54 + app.get(/.*/, (req, res) => {
55 + res.sendFile(path.join(__dirname, './static/index.html')); });
49 56
50 57 process.on('SIGTERM', shutdown);
51 58 process.on('SIGINT', shutdown);
api/src/routes/sleepRecords.ts
@@ -0,0 +1,11 @@
1 + import { Router } from "express";
2 + import { SleepRecordsController } from '@app/controllers/sleepRecords'
3 + import { requireAuth } from '@app/middleware/auth';
4 +
5 + const router = Router();
6 +
7 + router.get('/', requireAuth, SleepRecordsController.index);
8 + router.post('/', requireAuth, SleepRecordsController.create);
9 + router.delete('/:id', requireAuth, SleepRecordsController.destroy);
10 +
11 + export default router;
api/src/schemas/index.ts
@@ -22,3 +22,8 @@ type LoginInput,
22 22 type PasswordResetRequestInput,
23 23 type PasswordResetInput
24 24 } from '@app/schemas/auth.schema';
25 +
26 + export {
27 + type CreateSleepRecordInput,
28 + CreateSleepRecordSchema
29 + } from '@app/schemas/sleepRecord.schema';
api/src/schemas/sleepRecord.schema.ts
@@ -0,0 +1,9 @@
1 + import { z } from 'zod';
2 +
3 + export const CreateSleepRecordSchema = z.object({
4 + annotations: z.string().optional(),
5 + date: z.coerce.date(),
6 + average: z.number().min(0).max(15),
7 + });
8 +
9 + export type CreateSleepRecordInput = z.infer<typeof CreateSleepRecordSchema>;
api/src/services/sleepRecord.service.ts
@@ -0,0 +1,69 @@
1 + import { prisma } from '@app/lib/prisma';
2 + import { SleepRecord } from '@prisma/client';
3 +
4 + import { CreateSleepRecordInput } from '@app/schemas';
5 +
6 + export const createSleepRecord = async (
7 + userId: number, input: CreateSleepRecordInput
8 + ): Promise<SleepRecord> => {
9 + return prisma.sleepRecord.create({
10 + data: {
11 + ...input,
12 + userId
13 + }
14 + });
15 + };
16 +
17 + export const getSleepRecordsByUserId = async (
18 + userId: number,
19 + params?: {
20 + limit?: number;
21 + page?: number;
22 + from?: string;
23 + to?: string;
24 + }
25 + ): Promise<{ entries: SleepRecord[]; 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 + date: {
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.sleepRecord.findMany({
42 + where,
43 + orderBy: { date: 'desc' },
44 + take: limit,
45 + skip,
46 + }),
47 + prisma.sleepRecord.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 destroySleepRecordById = async (
61 + userId: number, id: number
62 + ): Promise<SleepRecord> => {
63 + return await prisma.sleepRecord.delete({
64 + where: {
65 + id,
66 + userId
67 + }
68 + })
69 + }