api: flesh sleep records endpoints
Pedro Lucas Porcellis porcellis@eletrotupi.com · 5 months ago
04f35e5e8f22150925526590ee8626257701a10b Browse filesParents
4ead651Changed files
7
Additions
+161
Deletions
-2
api/src/controllers/main.ts
1 1
import { Request, Response, NextFunction } from 'express';
2 2
3 3
export const MainController = {
4 4
// 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
}
12 12
}
13 13
};
/dev/null → api/src/controllers/sleepRecords.ts
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
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 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();
17 19
bootWorkers();
18 20
38 40
});
39 41
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);
52 59
53 60
return app;
55 62
56 63
const shutdown = async () => {
57 64
await Promise.all([closeAllWorkers(), closeAllQueues()]);
58 65
process.exit(0);
59 66
};
/dev/null → api/src/routes/sleepRecords.ts
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
1 1
export {
2 2
CreateMoodSchema,
3 3
MoodComponentSchema
4 4
} from '@app/schemas/mood.schema';
5 5
20 20
PasswordResetSchema,
21 21
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';
/dev/null → api/src/schemas/sleepRecord.schema.ts
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>;
/dev/null → api/src/services/sleepRecord.service.ts
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 +
}