"add PartyEvent API endpoints with service layer and test cases"

This commit is contained in:
louiscklaw
2025-06-15 15:01:18 +08:00
parent 9ac13787aa
commit 9943283eff
23 changed files with 728 additions and 49 deletions

View File

@@ -0,0 +1,40 @@
// src/app/api/party-event/create/route.ts
//
// PURPOSE:
// Create new party event in db
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { isDev } from 'src/constants';
import { createEvent } from 'src/app/services/party-event.service';
// ----------------------------------------------------------------------
/** **************************************
* POST - Create PartyEvent
*************************************** */
export async function POST(req: NextRequest) {
const { partyEventData } = await req.json();
try {
if (isDev) {
console.log({ partyEventData });
}
const created = await createEvent(partyEventData);
if (isDev) {
console.log('Event created successfully');
}
return response(created, STATUS.OK);
} catch (error) {
console.error('Error creating event:', { partyEventData });
return handleError('PartyEvent - Create', error);
}
}

View File

@@ -0,0 +1,34 @@
###
POST http://localhost:7272/api/party-event/create
Content-Type: application/json
{
"partyEventData": {
"title": "Summer Music Festival",
"description": "Annual summer music festival featuring local bands and artists",
"startDate": "2024-07-15T18:00:00Z",
"endDate": "2024-07-15T23:00:00Z",
"location": "Central Park, Hong Kong",
"coverUrl": "",
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"tags": [
"Music",
"Festival"
],
"status": "upcoming",
"capacity": 500,
"price": 150.00,
"organizer": "HK Music Society",
"category": "Music",
"isFeatured": true,
"registrationDeadline": "2024-07-10T00:00:00Z",
"requirements": "Age 18+",
"schedule": "18:00 Doors open\n19:00 First performance\n21:00 Main act",
"speakers": ["DJ Lee", "Band XYZ"],
"sponsors": ["HK Radio", "Music Magazine"]
}
}

View File

@@ -0,0 +1,22 @@
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { deleteEvent } from 'src/app/services/party-event.service';
/** **************************************
* PATCH - Delete PartyEvent
*************************************** */
export async function PATCH(req: NextRequest) {
try {
const { eventId } = await req.json();
if (!eventId) throw new Error('eventId cannot be null');
await deleteEvent(eventId);
return response({ eventId }, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Delete', error);
}
}

View File

@@ -0,0 +1,8 @@
###
PATCH http://localhost:7272/api/party-event/delete
Content-Type: application/json
{
"eventId": "e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01"
}

View File

@@ -0,0 +1,56 @@
// src/app/api/party-event/details/route.ts
//
// PURPOSE:
// Get party event from db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { L_INFO, L_ERROR } from 'src/constants';
import { getEvent } from 'src/app/services/party-event.service';
import { createAppLog } from 'src/app/services/app-log.service';
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
/**
**************************************
* GET PartyEvent detail
***************************************
*/
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const { searchParams } = req.nextUrl;
// RULES: eventId must exist
const eventId = searchParams.get('eventId');
if (!eventId) {
return response({ message: 'Event ID is required!' }, STATUS.BAD_REQUEST);
}
// NOTE: eventId confirmed exist, run below
const event = await getEvent(eventId);
if (!event) {
return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
}
logger('[PartyEvent] details', event.id);
createAppLog(L_INFO, 'Get event detail OK', debug);
return response({ event }, STATUS.OK);
} catch (error) {
createAppLog(L_ERROR, 'event detail error', debug);
return handleError('PartyEvent - Get details', error);
}
}

View File

@@ -0,0 +1,9 @@
###
# Get details for a specific party event
GET http://localhost:7272/api/party-event/details?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
###
# Alternative format with different ID
GET http://localhost:7272/api/party-event/details?eventId=evt_987654321

View File

@@ -0,0 +1,16 @@
import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
/**
***************************************
* GET - helloworld
***************************************
*/
export async function GET(req: NextRequest, res: NextResponse) {
try {
return response({ helloworld: 'party-event' }, STATUS.OK);
} catch (error) {
return handleError('Helloworld - Get all', error);
}
}

View File

@@ -0,0 +1,4 @@
###
GET /api/party-event/helloworld HTTP/1.1
Host: localhost:7272

View File

@@ -0,0 +1,38 @@
// src/app/api/party-event/list/route.ts
//
// PURPOSE:
// List all party events from db
//
// RULES:
// T.B.A.
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/app-log.service';
import { listEvents } from 'src/app/services/party-event.service';
import { flattenNextjsRequest } from '../../auth/sign-in/flattenNextjsRequest';
// ----------------------------------------------------------------------
/** **************************************
* GET - PartyEvents list
*************************************** */
export async function GET(req: NextRequest) {
const debug = { 'req.headers': flattenNextjsRequest(req) };
try {
const events = await listEvents();
createAppLog(L_INFO, 'party-event list ok', {});
return response({ events }, STATUS.OK);
} catch (error) {
createAppLog(L_ERROR, 'party-event list error', debug);
return handleError('PartyEvent - Get list', error);
}
}

View File

@@ -0,0 +1,14 @@
###
# Basic list all party events
GET http://localhost:7272/api/party-event/list
###
# List upcoming party events
GET http://localhost:7272/api/party-event/list?status=upcoming
###
# List featured party events
GET http://localhost:7272/api/party-event/list?isFeatured=true

View File

@@ -0,0 +1,75 @@
import type { NextRequest, NextResponse } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { listEvents, deleteEvent, updateEvent, createEvent } from 'src/app/services/party-event.service';
/**
**************************************
* GET - PartyEvent
***************************************
*/
export async function GET(req: NextRequest, res: NextResponse) {
try {
const events = await listEvents();
return response(events, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Get list', error);
}
}
/**
***************************************
* POST - Create PartyEvent
***************************************
*/
export async function POST(req: NextRequest) {
const OPERATION = 'PartyEvent - Create';
const { data } = await req.json();
try {
const event = await createEvent(data);
return response(OPERATION, STATUS.OK);
} catch (error) {
return handleError(OPERATION, error);
}
}
/**
***************************************
* PUT - Update PartyEvent
***************************************
*/
export async function PUT(req: NextRequest) {
const { searchParams } = req.nextUrl;
const eventId = searchParams.get('eventId');
const { data } = await req.json();
try {
if (!eventId) throw new Error('eventId cannot be null');
const result = await updateEvent(eventId, data);
return response(result, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Update', error);
}
}
/**
***************************************
* DELETE - Delete PartyEvent
***************************************
*/
export async function DELETE(req: NextRequest) {
const { searchParams } = req.nextUrl;
const eventId = searchParams.get('eventId');
try {
if (!eventId) throw new Error('eventId cannot be null');
await deleteEvent(eventId);
return response({ success: true }, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Delete', error);
}
}

View File

@@ -0,0 +1,34 @@
import type { NextRequest } from 'next/server';
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
import { IEventItem } from '../update/route';
// import { searchEvents } from 'src/app/services/party-event.service';
// ----------------------------------------------------------------------
/** **************************************
* GET - Search PartyEvents
*************************************** */
export async function GET(req: NextRequest) {
try {
const { searchParams } = req.nextUrl;
const query = searchParams.get('query')?.trim().toLowerCase();
if (!query) {
return response({ results: [] }, STATUS.OK);
}
const results: IEventItem[] = [];
// TODO: search party event not implemented
console.log('search party event not implemented');
// const results = await searchEvents(query);
logger('[PartyEvent] search-results', results?.length);
return response({ results }, STATUS.OK);
} catch (error) {
return handleError('PartyEvent - Get search', error);
}
}

View File

@@ -0,0 +1,24 @@
###
# Search party events by title
GET http://localhost:7272/api/party-event/search?query=Music
###
# Search party events by location
GET http://localhost:7272/api/party-event/search?query=Central+Park
###
# Search party events by tag
GET http://localhost:7272/api/party-event/search?query=Festival
###
# Combined search with multiple parameters
GET http://localhost:7272/api/party-event/search?query=Summer&location=Hong+Kong&category=Music
###
# No results expected
GET http://localhost:7272/api/party-event/search?query=zzzzzz

View File

@@ -0,0 +1,57 @@
// src/app/api/party-event/update/route.ts
//
// PURPOSE:
// Update party event in db by id
//
// RULES:
// T.B.A.
import type { NextRequest } from 'next/server';
import { STATUS, response, handleError } from 'src/utils/response';
import { updateEvent } from 'src/app/services/party-event.service';
// ----------------------------------------------------------------------
/** **************************************
* PUT - Update PartyEvent
*************************************** */
export async function PUT(req: NextRequest) {
const { partyEventData } = await req.json();
const { id } = partyEventData;
if (!id) return response({ message: 'id not found' }, STATUS.ERROR);
try {
const result = await updateEvent(id, partyEventData);
return response({ result }, STATUS.OK);
} catch (error) {
console.error('Error updating event:', { partyEventData });
return handleError('PartyEvent - Update', error);
}
}
export type IEventItem = {
id: string;
title: string;
description: string;
startDate: Date;
endDate: Date;
location: string;
coverUrl: string;
images: string[];
tags: string[];
status: string;
capacity: number;
price: number;
organizer: string;
category: string;
isFeatured: boolean;
registrationDeadline: Date;
requirements: string[];
schedule: string;
speakers: string[];
sponsors: string[];
};

View File

@@ -0,0 +1,35 @@
###
PUT http://localhost:7272/api/party-event/update
Content-Type: application/json
{
"partyEventData": {
"id":"e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01",
"title": "Summer Music Festival",
"description": "Annual summer music festival featuring local bands and artists",
"startDate": "2024-07-15T18:00:00Z",
"endDate": "2024-07-15T23:00:00Z",
"location": "Central Park, Hong Kong",
"coverUrl": "",
"images": [
"data:image/png;base64,C",
"data:image/png;base64,C"
],
"tags": [
"Music",
"Festival"
],
"status": "upcoming",
"capacity": 500,
"price": 150.00,
"organizer": "HK Music Society",
"category": "Music",
"isFeatured": true,
"registrationDeadline": "2024-07-10T00:00:00Z",
"requirements": "Age 18+",
"schedule": "18:00 Doors open\n19:00 First performance\n21:00 Main act",
"speakers": ["DJ Lee", "Band XYZ"],
"sponsors": ["HK Radio", "Music Magazine"]
}
}

View File

@@ -0,0 +1,92 @@
// src/app/services/party-event.service.ts
//
// PURPOSE:
// - Service for handling EventItem (PartyEvent) Record
//
import type { EventItem } from '@prisma/client';
import prisma from '../lib/prisma';
type CreateEvent = {
name: string;
title: string;
eventDate: Date;
location: string;
duration_m: number;
ageBottom?: number;
ageTop?: number;
currency?: string;
price?: number;
priceSale?: number;
coverUrl?: string;
images?: string[];
description?: string;
subDescription?: string;
publish?: string;
category?: string;
tags?: string[];
joinMembers?: any[];
};
type UpdateEvent = {
name?: string;
title?: string;
eventDate?: Date;
location?: string;
duration_m?: number;
ageBottom?: number;
ageTop?: number;
currency?: string;
price?: number;
priceSale?: number;
coverUrl?: string;
images?: string[];
description?: string;
subDescription?: string;
publish?: string;
category?: string;
tags?: string[];
joinMembers?: any[];
};
async function listEvents(): Promise<EventItem[]> {
return prisma.eventItem.findMany({
include: { reviews: true },
});
}
async function getEvent(eventId: string): Promise<EventItem | null> {
return prisma.eventItem.findUnique({
where: { id: eventId },
include: { reviews: true },
});
}
async function getEventByNameOrTitle(searchText: string): Promise<EventItem[] | null> {
return prisma.eventItem.findMany({
where: {
OR: [{ name: { contains: searchText, mode: 'insensitive' } }, { title: { contains: searchText, mode: 'insensitive' } }],
},
include: { reviews: true },
});
}
async function createEvent(eventData: any) {
return await prisma.eventItem.create({ data: eventData });
}
async function updateEvent(eventId: string, updateForm: UpdateEvent) {
return prisma.eventItem.update({
where: { id: eventId },
data: updateForm,
});
}
async function deleteEvent(eventId: string) {
return prisma.eventItem.delete({
where: { id: eventId },
});
}
export { getEvent, listEvents, createEvent, updateEvent, deleteEvent, getEventByNameOrTitle, type CreateEvent, type UpdateEvent };