diff --git a/03_source/cms_backend/src/app/api/AppLog/route.ts b/03_source/cms_backend/src/app/api/AppLog/route.ts index 6bb3316..5fd3ec6 100644 --- a/03_source/cms_backend/src/app/api/AppLog/route.ts +++ b/03_source/cms_backend/src/app/api/AppLog/route.ts @@ -25,6 +25,7 @@ export async function POST(req: NextRequest) { const { data } = await req.json(); try { + // TODO: obsolete createNewAppLog const createResult = await createNewAppLog(data); return response(createResult, STATUS.OK); diff --git a/03_source/cms_backend/src/app/api/auth/me/route.ts b/03_source/cms_backend/src/app/api/auth/me/route.ts index 7f2184d..18fa6b4 100644 --- a/03_source/cms_backend/src/app/api/auth/me/route.ts +++ b/03_source/cms_backend/src/app/api/auth/me/route.ts @@ -1,13 +1,20 @@ +import type { User } from '@prisma/client'; +import type { NextRequest } from 'next/server'; + import { headers } from 'next/headers'; import { verify } from 'src/utils/jwt'; import { STATUS, response, handleError } from 'src/utils/response'; -import { _users, JWT_SECRET } from 'src/_mock/_auth'; +import { JWT_SECRET } from 'src/_mock/_auth'; +import { getUserById } from 'src/app/services/user.service'; +import { createAccessLog } from 'src/app/services/AccessLog.service'; + +import { flattenNextjsRequest } from '../sign-in/flattenNextjsRequest'; // ---------------------------------------------------------------------- -export const runtime = 'edge'; +// export const runtime = 'edge'; /** * This API is used for demo purpose only @@ -17,25 +24,44 @@ export const runtime = 'edge'; * You should not expose the JWT_SECRET in the client side */ -export async function GET() { +const USER_TOKEN_CHECK_FAILED = 'user token check failed'; +const INVALID_AUTH_TOKEN = 'Invalid authorization token'; +const USER_ID_NOT_FOUND = 'userId not found'; +const USER_TOKEN_OK = 'user token check ok'; +const AUTHORIZATION_TOKEN_MISSING_OR_INVALID = 'Authorization token missing or invalid'; + +export async function GET(req: NextRequest) { + const debug = { 'req.headers': flattenNextjsRequest(req) }; + try { const headersList = headers(); const authorization = headersList.get('authorization'); if (!authorization || !authorization.startsWith('Bearer ')) { - return response({ message: 'Authorization token missing or invalid' }, STATUS.UNAUTHORIZED); + return response({ message: AUTHORIZATION_TOKEN_MISSING_OR_INVALID }, STATUS.UNAUTHORIZED); } const accessToken = `${authorization}`.split(' ')[1]; const data = await verify(accessToken, JWT_SECRET); + console.log(data.userId); - const currentUser = _users.find((user) => user.id === data.userId); + if (data.userId) { + // TODO: remove me + // const currentUser = _users.find((user) => user.id === data.userId); + const currentUser: User | null = await getUserById(data.userId); - if (!currentUser) { - return response({ message: 'Invalid authorization token' }, STATUS.UNAUTHORIZED); + if (!currentUser) { + createAccessLog('', USER_TOKEN_CHECK_FAILED, debug); + + return response({ message: INVALID_AUTH_TOKEN }, STATUS.UNAUTHORIZED); + } + + createAccessLog(currentUser.id, USER_TOKEN_OK, debug); + + return response({ user: currentUser }, STATUS.OK); + } else { + return response({ message: USER_ID_NOT_FOUND }, STATUS.ERROR); } - - return response({ user: currentUser }, 200); } catch (error) { return handleError('[Auth] - Me', error); } diff --git a/03_source/cms_backend/src/app/api/auth/me/test.http b/03_source/cms_backend/src/app/api/auth/me/test.http new file mode 100644 index 0000000..4bcb1a1 --- /dev/null +++ b/03_source/cms_backend/src/app/api/auth/me/test.http @@ -0,0 +1,25 @@ +### +# username and password ok +GET http://localhost:7272/api/auth/me +Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJjbWJnbnUyengwMDBjaHEzaGZ3dmtjejlvIiwiaWF0IjoxNzQ4OTY0ODkyLCJleHAiOjE3NTAxNzQ0OTJ9.lo04laCxtm0IVeYaETEV3hXKyDmXPEn7SyWtY2VR4dI + + +### +# 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-in/flattenNextjsRequest.ts b/03_source/cms_backend/src/app/api/auth/sign-in/flattenNextjsRequest.ts new file mode 100644 index 0000000..68cfabf --- /dev/null +++ b/03_source/cms_backend/src/app/api/auth/sign-in/flattenNextjsRequest.ts @@ -0,0 +1,5 @@ +import type { NextRequest } from 'next/server'; + +export function flattenNextjsRequest(req: NextRequest) { + return Object.fromEntries(req.headers.entries()); +} 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 d7dec3d..68eaee0 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 @@ -7,6 +7,7 @@ import { JWT_SECRET, JWT_EXPIRES_IN } from 'src/_mock/_auth'; import { createAccessLog } from 'src/app/services/AccessLog.service'; import prisma from '../../../lib/prisma'; +import { flattenNextjsRequest } from './flattenNextjsRequest'; // ---------------------------------------------------------------------- @@ -22,7 +23,7 @@ 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()) }; + const debug = { 'req.headers': flattenNextjsRequest(req) }; try { const { email, password } = await req.json(); diff --git a/03_source/cms_backend/src/app/api/event/_GUIDELINES.md b/03_source/cms_backend/src/app/api/event/_GUIDELINES.md new file mode 100644 index 0000000..219fb1c --- /dev/null +++ b/03_source/cms_backend/src/app/api/event/_GUIDELINES.md @@ -0,0 +1,25 @@ +# GUIDELINE + +## Event / event + +- this is a `event` api endpoint +- this is a demo to handle `event` record +- use single file for single db table/collection only + +## `route.ts` + +handle `GET`, `POST`, `PUT`, `DELETE` + +## `test.http` + +store test request + +## `../../services/event.service.ts` + +event schema CRUD handler + +`listEvents` - list `event` record +`getEvent` - get `event` record by id +`createNewEvent` - create `event` record +`updateEvent` - update `event` record by id +`deleteEvent` - delete `event` record by id diff --git a/03_source/cms_backend/src/app/api/event/details/route.ts b/03_source/cms_backend/src/app/api/event/details/route.ts index be0e574..c4db193 100644 --- a/03_source/cms_backend/src/app/api/event/details/route.ts +++ b/03_source/cms_backend/src/app/api/event/details/route.ts @@ -8,10 +8,11 @@ import type { NextRequest } from 'next/server'; -import { logger } from 'src/utils/logger'; import { STATUS, response, handleError } from 'src/utils/response'; -import prisma from '../../../lib/prisma'; +import { L_INFO, L_ERROR } from 'src/constants'; +import { getEvent } from 'src/app/services/eventItem.service'; +import { createAppLog } from 'src/app/services/AppLog.service'; // ---------------------------------------------------------------------- @@ -19,29 +20,32 @@ import prisma from '../../../lib/prisma'; * GET Event detail *************************************** */ export async function GET(req: NextRequest) { - try { - const { searchParams } = req.nextUrl; + const debug = { 'req.headers': Object.fromEntries(req.headers.entries()) }; - // RULES: eventId must exist - const eventId = searchParams.get('eventId'); + const { searchParams } = req.nextUrl; + // RULES: for the incoming request, the `eventId` must exist + const eventId = searchParams.get('eventId'); + + try { if (!eventId) { return response({ message: 'Event ID is required!' }, STATUS.BAD_REQUEST); } - // NOTE: eventId confirmed exist, run below - const event = await prisma.eventItem.findFirst({ - include: { reviews: true }, - where: { id: eventId }, - }); + // NOTE: `eventId` confirmed exist, run below + const event = await getEvent(eventId); + console.log({ event }); + // RULES: show error if not found if (!event) { return response({ message: 'Event not found!' }, STATUS.NOT_FOUND); } - logger('[Event] details', event.id); + // logger('[Event] details', event.id); + await createAppLog(L_INFO, 'get event detail ok', { eventId }); return response({ event }, STATUS.OK); } catch (error) { + await createAppLog(L_ERROR, 'error during getting event detail', { debug, eventId }); return handleError('Event - Get details', error); } } diff --git a/03_source/cms_backend/src/app/api/event/details/test.http b/03_source/cms_backend/src/app/api/event/details/test.http index 9df1c50..8f3b9e8 100644 --- a/03_source/cms_backend/src/app/api/event/details/test.http +++ b/03_source/cms_backend/src/app/api/event/details/test.http @@ -1,3 +1,5 @@ ### - GET http://localhost:7272/api/event/details?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01 + +### +GET http://localhost:7272/api/event/details diff --git a/03_source/cms_backend/src/app/api/event/list/route.ts b/03_source/cms_backend/src/app/api/event/list/route.ts index a5784a9..3cba5fd 100644 --- a/03_source/cms_backend/src/app/api/event/list/route.ts +++ b/03_source/cms_backend/src/app/api/event/list/route.ts @@ -1,17 +1,17 @@ -// src/app/api/event/list/route.ts import { logger } from 'src/utils/logger'; import { STATUS, response, handleError } from 'src/utils/response'; -import prisma from '../../../lib/prisma'; +// src/app/api/event/list/route.ts +import { listEvents } from 'src/app/services/eventItem.service'; // ---------------------------------------------------------------------- /** ************************************** - * GET - Events + * GET - Events, obsoleted *************************************** */ export async function GET() { try { - const events = await prisma.eventItem.findMany(); + const events = await listEvents(); logger('[Event] list', events.length); diff --git a/03_source/cms_backend/src/app/api/helloworld/detail/route.ts b/03_source/cms_backend/src/app/api/helloworld/detail/route.ts index aad3cef..3ea4445 100644 --- a/03_source/cms_backend/src/app/api/helloworld/detail/route.ts +++ b/03_source/cms_backend/src/app/api/helloworld/detail/route.ts @@ -10,7 +10,11 @@ import type { NextRequest } from 'next/server'; import { STATUS, response, handleError } from 'src/utils/response'; +import { L_INFO, L_ERROR } from 'src/constants'; +import { createAppLog } from 'src/app/services/AppLog.service'; + import prisma from '../../../lib/prisma'; +import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest'; // ---------------------------------------------------------------------- @@ -21,6 +25,8 @@ import prisma from '../../../lib/prisma'; */ export async function GET(req: NextRequest) { // Original user details functionality + const debug = { 'req.headers': flattenNextjsRequest(req) }; + try { const { searchParams } = req.nextUrl; @@ -32,8 +38,12 @@ export async function GET(req: NextRequest) { if (!helloworld) return response({ message: 'User not found!' }, STATUS.NOT_FOUND); + createAppLog(L_INFO, 'Get OK', debug); + return response({ helloworld }, STATUS.OK); } catch (error) { + createAppLog(L_ERROR, 'Get error', debug); + return handleError('Product - Get details', error); } } diff --git a/03_source/cms_backend/src/app/api/product/_GUIDELINES.md b/03_source/cms_backend/src/app/api/product/_GUIDELINES.md new file mode 100644 index 0000000..4b95137 --- /dev/null +++ b/03_source/cms_backend/src/app/api/product/_GUIDELINES.md @@ -0,0 +1,3 @@ +# GUIDELINES + +T.B.A. diff --git a/03_source/cms_backend/src/app/api/product/details/route.ts b/03_source/cms_backend/src/app/api/product/details/route.ts index f75d47c..beb7197 100644 --- a/03_source/cms_backend/src/app/api/product/details/route.ts +++ b/03_source/cms_backend/src/app/api/product/details/route.ts @@ -1,7 +1,7 @@ // src/app/api/product/details/route.ts // // PURPOSE: -// save product to db by id +// get product from db by id // // RULES: // T.B.A. @@ -11,14 +11,22 @@ import type { NextRequest } from 'next/server'; import { logger } from 'src/utils/logger'; import { STATUS, response, handleError } from 'src/utils/response'; -import prisma from '../../../lib/prisma'; +import { L_INFO, L_ERROR } from 'src/constants'; +import { getProduct } from 'src/app/services/product.service'; +import { createAppLog } from 'src/app/services/AppLog.service'; + +import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest'; // ---------------------------------------------------------------------- -/** ************************************** +/** + ************************************** * GET Product detail - *************************************** */ + *************************************** + */ export async function GET(req: NextRequest) { + const debug = { 'req.headers': flattenNextjsRequest(req) }; + try { const { searchParams } = req.nextUrl; @@ -29,10 +37,7 @@ export async function GET(req: NextRequest) { } // NOTE: productId confirmed exist, run below - const product = await prisma.productItem.findFirst({ - include: { reviews: true }, - where: { id: productId }, - }); + const product = await getProduct(productId); if (!product) { return response({ message: 'Product not found!' }, STATUS.NOT_FOUND); @@ -40,8 +45,12 @@ export async function GET(req: NextRequest) { logger('[Product] details', product.id); + createAppLog(L_INFO, 'Get product detail OK', debug); + return response({ product }, STATUS.OK); } catch (error) { + createAppLog(L_ERROR, 'product detail error', debug); + return handleError('Product - Get details', error); } } diff --git a/03_source/cms_backend/src/app/api/product/details/test.http b/03_source/cms_backend/src/app/api/product/details/test.http new file mode 100644 index 0000000..da42439 --- /dev/null +++ b/03_source/cms_backend/src/app/api/product/details/test.http @@ -0,0 +1,3 @@ +### + +GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01 diff --git a/03_source/cms_backend/src/app/api/product/list/route.ts b/03_source/cms_backend/src/app/api/product/list/route.ts index 027d97b..5e553df 100644 --- a/03_source/cms_backend/src/app/api/product/list/route.ts +++ b/03_source/cms_backend/src/app/api/product/list/route.ts @@ -1,22 +1,41 @@ // src/app/api/product/list/route.ts -import { logger } from 'src/utils/logger'; +// +// PURPOSE: +// save product to db by id +// +// RULES: +// T.B.A. + +import type { NextRequest } from 'next/server'; + import { STATUS, response, handleError } from 'src/utils/response'; -import prisma from '../../../lib/prisma'; +import { L_INFO, L_ERROR } from 'src/constants'; +import { createAppLog } from 'src/app/services/AppLog.service'; +import { listProducts } from 'src/app/services/product.service'; + +import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest'; // ---------------------------------------------------------------------- /** ************************************** - * GET - Products + * GET - Products list *************************************** */ -export async function GET() { - try { - const products = await prisma.productItem.findMany(); +export async function GET(req: NextRequest) { + const debug = { 'req.headers': flattenNextjsRequest(req) }; - logger('[Product] list', products.length); + try { + // const products = await prisma.productItem.findMany(); + const products = await listProducts(); + + // logger('[Product] list', products.length); + + createAppLog(L_INFO, 'product list ok', {}); return response({ products }, STATUS.OK); } catch (error) { + createAppLog(L_ERROR, 'product list error', debug); + return handleError('Product - Get list', error); } } diff --git a/03_source/cms_backend/src/app/api/product/list/test.http b/03_source/cms_backend/src/app/api/product/list/test.http new file mode 100644 index 0000000..11f2a7b --- /dev/null +++ b/03_source/cms_backend/src/app/api/product/list/test.http @@ -0,0 +1,3 @@ +### + +GET http://localhost:7272/api/product/list diff --git a/03_source/cms_backend/src/app/api/student/_GUIDELINES.md b/03_source/cms_backend/src/app/api/student/_GUIDELINES.md new file mode 100644 index 0000000..5695938 --- /dev/null +++ b/03_source/cms_backend/src/app/api/student/_GUIDELINES.md @@ -0,0 +1,23 @@ +# GUIDELINE + +- this is a helloworld api endpoint +- this is a demo to handle helloworld record +- use single file for single db table/collection only + +## `route.ts` + +handle `GET`, `POST`, `PUT`, `DELETE` + +## `test.http` + +store test request + +## `../../services/helloworld.service.ts` + +helloworld schema CRUD handler + +`listHelloworlds` - list helloworld record +`getHelloworld` - get helloworld record by id +`createNewHelloworld` - create helloworld record +`updateHelloworld` - update helloworld record by id +`deleteHelloworld` - delete helloworld record by id diff --git a/03_source/cms_backend/src/app/api/student/detail/route.ts b/03_source/cms_backend/src/app/api/student/detail/route.ts new file mode 100644 index 0000000..aad3cef --- /dev/null +++ b/03_source/cms_backend/src/app/api/student/detail/route.ts @@ -0,0 +1,39 @@ +// src/app/api/helloworld/detail/route.ts +// +// PURPOSE: +// Get single helloworld record detail +// +// RULES: +// - For helloworld requests, return simple response +// +import type { NextRequest } from 'next/server'; + +import { STATUS, response, handleError } from 'src/utils/response'; + +import prisma from '../../../lib/prisma'; + +// ---------------------------------------------------------------------- + +/** + ************************************** + * GET - Handle both helloworld and user details + ************************************** + */ +export async function GET(req: NextRequest) { + // Original user details functionality + try { + const { searchParams } = req.nextUrl; + + // RULES: helloworldId must exist + const helloworldId = searchParams.get('helloworldId'); + if (!helloworldId) return response({ message: 'helloworldId is required!' }, STATUS.BAD_REQUEST); + + const helloworld = await prisma.userItem.findFirst({ where: { id: helloworldId } }); + + if (!helloworld) return response({ message: 'User not found!' }, STATUS.NOT_FOUND); + + return response({ helloworld }, STATUS.OK); + } catch (error) { + return handleError('Product - Get details', error); + } +} diff --git a/03_source/cms_backend/src/app/api/student/detail/test.http b/03_source/cms_backend/src/app/api/student/detail/test.http new file mode 100644 index 0000000..60c39cc --- /dev/null +++ b/03_source/cms_backend/src/app/api/student/detail/test.http @@ -0,0 +1,4 @@ +### + +GET http://localhost:7272/api/helloworld/details?helloworldId=1165ce3a-29b8-4e1a-9148-1ae08d7e8e01 + diff --git a/03_source/cms_backend/src/app/api/student/route.ts b/03_source/cms_backend/src/app/api/student/route.ts new file mode 100644 index 0000000..fae88b9 --- /dev/null +++ b/03_source/cms_backend/src/app/api/student/route.ts @@ -0,0 +1,34 @@ +import prisma from '@/lib/prisma'; +import { NextResponse } from 'next/server'; + +// GET: 获取所有学生 +export async function GET() { + try { + const students = await prisma.student.findMany(); + return NextResponse.json(students); + } catch (error) { + return NextResponse.json({ error: 'Failed to fetch students' }, { status: 500 }); + } +} + +// POST: 创建新学生 +export async function POST(request: Request) { + try { + const { email, metadata } = await request.json(); + + if (!email) { + return NextResponse.json({ error: 'Email is required' }, { status: 400 }); + } + + const student = await prisma.student.create({ + data: { + email, + metadata: metadata || {}, + }, + }); + + return NextResponse.json(student, { status: 201 }); + } catch (error) { + return NextResponse.json({ error: 'Failed to create student' }, { status: 500 }); + } +} diff --git a/03_source/cms_backend/src/app/api/student/test.http b/03_source/cms_backend/src/app/api/student/test.http new file mode 100644 index 0000000..2ef9402 --- /dev/null +++ b/03_source/cms_backend/src/app/api/student/test.http @@ -0,0 +1,25 @@ +### +GET http://localhost:7272/api/helloworld + +### +GET http://localhost:7272/api/helloworld?helloworldId=1 + +### +POST http://localhost:7272/api/helloworld?helloworldId=1 +content-type: application/json + +{ + "data":{"hello": "hell"} +} + +### +PUT http://localhost:7272/api/helloworld?helloworldId=1 +content-type: application/json + +{ + "data": {"hello": "hell"} +} + + +### +DELETE http://localhost:7272/api/helloworld?helloworldId=1