From 5480b621318d33897151af3bcc60ee00b5e24ec4 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Tue, 3 Jun 2025 15:27:56 +0800 Subject: [PATCH] "feat: implement AccessLog and AppLog APIs with CRUD operations and test cases" --- .../src/app/api/AccessLog/_GUIDELINES.md | 24 ++++++ .../src/app/api/AccessLog/detail/route.ts | 38 +++++++++ .../src/app/api/AccessLog/detail/test.http | 7 ++ .../src/app/api/AccessLog/route.ts | 76 ++++++++++++++++++ .../src/app/api/AccessLog/test.http | 33 ++++++++ .../src/app/api/AppLog/_GUIDELINES.md | 24 ++++++ .../src/app/api/AppLog/detail/route.ts | 39 +++++++++ .../src/app/api/AppLog/detail/test.http | 7 ++ .../cms_backend/src/app/api/AppLog/route.ts | 80 +++++++++++++++++++ .../cms_backend/src/app/api/AppLog/test.http | 25 ++++++ .../src/app/api/auth/sign-in/route.ts | 30 ++++--- .../src/app/api/auth/sign-in/test.http | 29 +++++++ .../src/app/api/auth/sign-up/route.ts | 5 +- 13 files changed, 402 insertions(+), 15 deletions(-) create mode 100644 03_source/cms_backend/src/app/api/AccessLog/_GUIDELINES.md create mode 100644 03_source/cms_backend/src/app/api/AccessLog/detail/route.ts create mode 100644 03_source/cms_backend/src/app/api/AccessLog/detail/test.http create mode 100644 03_source/cms_backend/src/app/api/AccessLog/route.ts create mode 100644 03_source/cms_backend/src/app/api/AccessLog/test.http create mode 100644 03_source/cms_backend/src/app/api/AppLog/_GUIDELINES.md create mode 100644 03_source/cms_backend/src/app/api/AppLog/detail/route.ts create mode 100644 03_source/cms_backend/src/app/api/AppLog/detail/test.http create mode 100644 03_source/cms_backend/src/app/api/AppLog/route.ts create mode 100644 03_source/cms_backend/src/app/api/AppLog/test.http create mode 100644 03_source/cms_backend/src/app/api/auth/sign-in/test.http diff --git a/03_source/cms_backend/src/app/api/AccessLog/_GUIDELINES.md b/03_source/cms_backend/src/app/api/AccessLog/_GUIDELINES.md new file mode 100644 index 0000000..7b9a93c --- /dev/null +++ b/03_source/cms_backend/src/app/api/AccessLog/_GUIDELINES.md @@ -0,0 +1,24 @@ +# GUIDELINE + +- this is a AppLog api endpoint +- this is a demo to handle AppLog record +- use single file for single db table/collection only + +## `route.ts` + +`route.ts` - handle `GET`, `POST`, `PUT`, `DELETE` +`detail/route.ts` - handle `GET` request of specific `AppLog` by id + +## `test.http` + +store test request + +## `../../services/AppLog.service.ts` + +AppLog schema CRUD handler + +`listAppLogs` - list AppLog record +`getAppLog` - get AppLog record by id +`createNewAppLog` - create AppLog record +`updateAppLog` - update AppLog record by id +`deleteAppLog` - delete AppLog record by id diff --git a/03_source/cms_backend/src/app/api/AccessLog/detail/route.ts b/03_source/cms_backend/src/app/api/AccessLog/detail/route.ts new file mode 100644 index 0000000..4b9ea59 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AccessLog/detail/route.ts @@ -0,0 +1,38 @@ +// src/app/api/AccessLog/detail/route.ts +// +// PURPOSE: +// Get single AccessLog record detail +// +// RULES: +// - Return complete AccessLog details +// +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import { AccessLogService } from '../../../../modules/AccessLog/AccessLog.service'; + +// ---------------------------------------------------------------------- + +/** + ************************************** + * GET - Get AccessLog details + ************************************** + */ +export async function GET(req: NextRequest) { + try { + const { searchParams } = req.nextUrl; + + // RULES: accessLogId must exist + const accessLogId = searchParams.get('accessLogId'); + if (!accessLogId) return response({ message: 'accessLogId is required!' }, STATUS.BAD_REQUEST); + + const accessLog = await AccessLogService.findById(accessLogId); + + if (!accessLog) return response({ message: 'AccessLog not found!' }, STATUS.NOT_FOUND); + + return response({ accessLog }, STATUS.OK); + } catch (error) { + return handleError('AccessLog - Get details', error); + } +} diff --git a/03_source/cms_backend/src/app/api/AccessLog/detail/test.http b/03_source/cms_backend/src/app/api/AccessLog/detail/test.http new file mode 100644 index 0000000..3db4211 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AccessLog/detail/test.http @@ -0,0 +1,7 @@ +### + +GET http://localhost:7272/api/AppLog/detail?appLogId=e1bb8e4a-da37-4dbc-b8f2-9ad233a102ad + +### + +GET http://localhost:7272/api/AppLog/detail?start=2025-01-01&end=2025-12-31 diff --git a/03_source/cms_backend/src/app/api/AccessLog/route.ts b/03_source/cms_backend/src/app/api/AccessLog/route.ts new file mode 100644 index 0000000..d71b30c --- /dev/null +++ b/03_source/cms_backend/src/app/api/AccessLog/route.ts @@ -0,0 +1,76 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import { listAccessLogs } from 'src/app/services/AccessLog.service'; + +// import prisma from '../../lib/prisma'; + +export async function GET(req: NextRequest, res: NextResponse) { + try { + const result = await listAccessLogs(); + + return response(result, STATUS.OK); + } catch (error) { + return handleError('AccessLog - Get all', error); + } +} + +// /** +// *************************************** +// * POST - create AccessLog +// *************************************** +// */ +// export async function POST(req: NextRequest) { +// const { data } = await req.json(); + +// try { +// const createResult = await AccessLogService.create(data); + +// return response(createResult, STATUS.OK); +// } catch (error) { +// return handleError('AccessLog - Create', error); +// } +// } + +// /** +// *************************************** +// * PUT - update AccessLog +// *************************************** +// */ +// export async function PUT(req: NextRequest) { +// const { searchParams } = req.nextUrl; +// const accessLogId = searchParams.get('accessLogId'); + +// const { data } = await req.json(); + +// try { +// if (!accessLogId) throw new Error('accessLogId cannot be null'); + +// const updateResult = await AccessLogService.update(accessLogId, data); + +// return response(updateResult, STATUS.OK); +// } catch (error) { +// return handleError('AccessLog - Update', error); +// } +// } + +// /** +// *************************************** +// * DELETE - delete AccessLog +// *************************************** +// */ +// export async function DELETE(req: NextRequest) { +// const { searchParams } = req.nextUrl; +// const accessLogId = searchParams.get('accessLogId'); + +// try { +// if (!accessLogId) throw new Error('accessLogId cannot be null'); + +// const deleteResult = await AccessLogService.delete(accessLogId); + +// return response(deleteResult, STATUS.OK); +// } catch (error) { +// return handleError('AccessLog - Delete', error); +// } +// } diff --git a/03_source/cms_backend/src/app/api/AccessLog/test.http b/03_source/cms_backend/src/app/api/AccessLog/test.http new file mode 100644 index 0000000..e8c2f8d --- /dev/null +++ b/03_source/cms_backend/src/app/api/AccessLog/test.http @@ -0,0 +1,33 @@ +### +GET http://localhost:7272/api/AccessLog + +### +GET http://localhost:7272/api/AccessLog?accessLogId=51f2f5dd-78be-4069-ba29-09d2a5026191 + +### +POST http://localhost:7272/api/AccessLog +content-type: application/json + +{ + "data": { + "userId": "user123", + "ipAddress": "192.168.1.1", + "userAgent": "Mozilla/5.0", + "action": "LOGIN", + "path": "/api/auth/login", + "status": 200 + } +} + +### +PUT http://localhost:7272/api/AccessLog?accessLogId=51f2f5dd-78be-4069-ba29-09d2a5026191 +content-type: application/json + +{ + "data": { + "status": 404 + } +} + +### +DELETE http://localhost:7272/api/AccessLog?accessLogId=51f2f5dd-78be-4069-ba29-09d2a5026191 diff --git a/03_source/cms_backend/src/app/api/AppLog/_GUIDELINES.md b/03_source/cms_backend/src/app/api/AppLog/_GUIDELINES.md new file mode 100644 index 0000000..7b9a93c --- /dev/null +++ b/03_source/cms_backend/src/app/api/AppLog/_GUIDELINES.md @@ -0,0 +1,24 @@ +# GUIDELINE + +- this is a AppLog api endpoint +- this is a demo to handle AppLog record +- use single file for single db table/collection only + +## `route.ts` + +`route.ts` - handle `GET`, `POST`, `PUT`, `DELETE` +`detail/route.ts` - handle `GET` request of specific `AppLog` by id + +## `test.http` + +store test request + +## `../../services/AppLog.service.ts` + +AppLog schema CRUD handler + +`listAppLogs` - list AppLog record +`getAppLog` - get AppLog record by id +`createNewAppLog` - create AppLog record +`updateAppLog` - update AppLog record by id +`deleteAppLog` - delete AppLog record by id diff --git a/03_source/cms_backend/src/app/api/AppLog/detail/route.ts b/03_source/cms_backend/src/app/api/AppLog/detail/route.ts new file mode 100644 index 0000000..3107892 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AppLog/detail/route.ts @@ -0,0 +1,39 @@ +// src/app/api/AppLog/detail/route.ts +// +// PURPOSE: +// Get single AppLog record detail +// +// RULES: +// - Return complete AppLog details +// +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** + ************************************** + * GET - Get AppLog details + ************************************** + */ +export async function GET(req: NextRequest) { + // Original user details functionality + try { + const { searchParams } = req.nextUrl; + + // RULES: appLogId must exist + const appLogId = searchParams.get('appLogId'); + if (!appLogId) return response({ message: 'appLogId is required!' }, STATUS.BAD_REQUEST); + + const appLog = await prisma.appLog.findFirst({ where: { id: appLogId } }); + + if (!appLog) return response({ message: 'AppLog not found!' }, STATUS.NOT_FOUND); + + return response({ appLog }, STATUS.OK); + } catch (error) { + return handleError('AppLog - Get details', error); + } +} diff --git a/03_source/cms_backend/src/app/api/AppLog/detail/test.http b/03_source/cms_backend/src/app/api/AppLog/detail/test.http new file mode 100644 index 0000000..3db4211 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AppLog/detail/test.http @@ -0,0 +1,7 @@ +### + +GET http://localhost:7272/api/AppLog/detail?appLogId=e1bb8e4a-da37-4dbc-b8f2-9ad233a102ad + +### + +GET http://localhost:7272/api/AppLog/detail?start=2025-01-01&end=2025-12-31 diff --git a/03_source/cms_backend/src/app/api/AppLog/route.ts b/03_source/cms_backend/src/app/api/AppLog/route.ts new file mode 100644 index 0000000..6bb3316 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AppLog/route.ts @@ -0,0 +1,80 @@ +import type { NextRequest, NextResponse } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import { listAppLogs, deleteAppLog, updateAppLog, createNewAppLog } from 'src/app/services/AppLog.service'; + +// import prisma from '../../lib/prisma'; + +export async function GET(req: NextRequest, res: NextResponse) { + try { + const result = await listAppLogs(); + + return response(result, STATUS.OK); + } catch (error) { + return handleError('Post - Get latest', error); + } +} + +/** + *************************************** + * POST - create AppLog + *************************************** + */ +export async function POST(req: NextRequest) { + const { data } = await req.json(); + + try { + const createResult = await createNewAppLog(data); + + return response(createResult, STATUS.OK); + } catch (error) { + return handleError('AppLog - Create', error); + } +} + +/** + *************************************** + * PUT - update AppLog + *************************************** + */ +export async function PUT(req: NextRequest) { + const { searchParams } = req.nextUrl; + const appLogId = searchParams.get('appLogId'); + + const { data } = await req.json(); + + try { + if (!appLogId) throw new Error('appLogId cannot null'); + const id: number = parseInt(appLogId); + + const updateResult = await updateAppLog(id, data); + + return response(updateResult, STATUS.OK); + } catch (error) { + return handleError('AppLog - Update', error); + } +} + +/** + *************************************** + * DELETE - update AppLog + *************************************** + */ +export async function DELETE(req: NextRequest) { + const { searchParams } = req.nextUrl; + const appLogId = searchParams.get('appLogId'); + + const { data } = await req.json(); + + try { + if (!appLogId) throw new Error('appLogId cannot null'); + const id: number = parseInt(appLogId); + + const deleteResult = await deleteAppLog(id); + + return response(deleteResult, STATUS.OK); + } catch (error) { + return handleError('AppLog - Update', error); + } +} diff --git a/03_source/cms_backend/src/app/api/AppLog/test.http b/03_source/cms_backend/src/app/api/AppLog/test.http new file mode 100644 index 0000000..08c2315 --- /dev/null +++ b/03_source/cms_backend/src/app/api/AppLog/test.http @@ -0,0 +1,25 @@ +### +GET http://localhost:7272/api/AppLog + +### +GET http://localhost:7272/api/AppLog?appLogId=51f2f5dd-78be-4069-ba29-09d2a5026191 + +### +POST http://localhost:7272/api/AppLog?appLogId=1 +content-type: application/json + +{ + "data":{"hello": "test"} +} + +### +PUT http://localhost:7272/api/AppLog?appLogId=1 +content-type: application/json + +{ + "data": {"hello": "test"} +} + + +### +DELETE http://localhost:7272/api/AppLog?appLogId=1 diff --git a/03_source/cms_backend/src/app/api/auth/sign-in/route.ts b/03_source/cms_backend/src/app/api/auth/sign-in/route.ts index 2571d64..d7dec3d 100644 --- a/03_source/cms_backend/src/app/api/auth/sign-in/route.ts +++ b/03_source/cms_backend/src/app/api/auth/sign-in/route.ts @@ -3,12 +3,13 @@ import type { NextRequest } from 'next/server'; import { sign } from 'src/utils/jwt'; import { STATUS, response, handleError } from 'src/utils/response'; -import { _users, JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth'; +import { JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth'; +import { createAccessLog } from 'src/app/services/AccessLog.service'; + +import prisma from '../../../lib/prisma'; // ---------------------------------------------------------------------- -export const runtime = 'edge'; - /** * This API is used for demo purpose only * You should use a real database @@ -17,29 +18,36 @@ export const runtime = 'edge'; * You should not expose the JWT_SECRET in the client side */ +const ERR_USER_NOT_FOUND = 'There is no user corresponding to the email address.'; +const ERR_WRONG_PASSWORD = 'Wrong password'; + export async function POST(req: NextRequest) { + const debug = { 'req.headers': Object.fromEntries(req.headers.entries()) }; + try { const { email, password } = await req.json(); - const currentUser = _users.find((user) => user.email === email); - + const currentUser = await prisma.user.findFirst({ where: { email } }); if (!currentUser) { - return response( - { message: 'There is no user corresponding to the email address.' }, - STATUS.UNAUTHORIZED - ); + await createAccessLog('', `user tried login with email ${email}`, { debug }); + return response({ message: ERR_USER_NOT_FOUND }, STATUS.UNAUTHORIZED); } if (currentUser?.password !== password) { - return response({ message: 'Wrong password' }, STATUS.UNAUTHORIZED); + await createAccessLog(currentUser.id, 'user logged with wrong password', { debug }); + return response({ message: ERR_WRONG_PASSWORD }, STATUS.UNAUTHORIZED); } const accessToken = await sign({ userId: currentUser?.id }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN, }); - return response({ user: currentUser, accessToken }, 200); + await createAccessLog(currentUser.id, 'access granted', { debug }); + + return response({ user: currentUser, accessToken }, STATUS.OK); } catch (error) { + await createAccessLog('', 'attempted login but failed', { debug, error }); + return handleError('Auth - Sign in', error); } } diff --git a/03_source/cms_backend/src/app/api/auth/sign-in/test.http b/03_source/cms_backend/src/app/api/auth/sign-in/test.http new file mode 100644 index 0000000..74975a0 --- /dev/null +++ b/03_source/cms_backend/src/app/api/auth/sign-in/test.http @@ -0,0 +1,29 @@ +### +# username and password ok +POST http://localhost:7272/api/auth/sign-in +content-type: application/json + +{ + "email": "demo@minimals.cc", + "password": "@2Minimal" +} + +### +# There is no user corresponding to the email address. +POST http://localhost:7272/api/auth/sign-in +content-type: application/json + +{ + "email": "demo@minimals1.cc", + "password": "@2Minimal" +} + +### +# Wrong password +POST http://localhost:7272/api/auth/sign-in +content-type: application/json + +{ + "email": "demo@minimals.cc", + "password": "@2Min111imal" +} diff --git a/03_source/cms_backend/src/app/api/auth/sign-up/route.ts b/03_source/cms_backend/src/app/api/auth/sign-up/route.ts index f172456..c0d1e9e 100644 --- a/03_source/cms_backend/src/app/api/auth/sign-up/route.ts +++ b/03_source/cms_backend/src/app/api/auth/sign-up/route.ts @@ -24,10 +24,7 @@ export async function POST(req: NextRequest) { const userExists = _users.find((user) => user.email === email); if (userExists) { - return response( - { message: 'There already exists an account with the given email address.' }, - STATUS.CONFLICT - ); + return response({ message: 'There already exists an account with the given email address.' }, STATUS.CONFLICT); } const newUser = {