diff --git a/03_source/cms_backend/prisma/schema.prisma b/03_source/cms_backend/prisma/schema.prisma
index f4fa608..07c4d71 100644
--- a/03_source/cms_backend/prisma/schema.prisma
+++ b/03_source/cms_backend/prisma/schema.prisma
@@ -46,6 +46,9 @@ model Session {
model User {
id String @id @default(cuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ //
name String?
username String? @unique
email String @unique
@@ -54,8 +57,6 @@ model User {
image String?
bucketImage String?
admin Boolean @default(false)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
info Json?
@@ -70,6 +71,9 @@ model VerificationToken {
@@unique([identifier, token])
}
+// ----------------------------------------------------------------
+// frontend/src/_mock/_user.ts
+
// ----------------------------------------------------------------
// REQ0044/near_by_page
@@ -99,6 +103,9 @@ model Member {
music String[] @default([])
pets String[] @default([])
character String[] @default([])
+ //
+ avatar String @default("")
+ sex String @default("")
}
model Order {
@@ -118,7 +125,7 @@ model Event {
id Int @id @default(autoincrement())
eventDate DateTime
title String
- joinMembers Member[] // Assuming Member model exists
+ joinMembers Json[] // Assuming Member model exists
price Float
currency String
duration_m Int
@@ -127,6 +134,8 @@ model Event {
location String
avatar String // Assuming avatar is stored as a file path or URL
Order Order[]
+ Member Member? @relation(fields: [memberId], references: [id])
+ memberId Int?
}
// cms_backend
@@ -537,6 +546,8 @@ model UserItem {
//
username String
password String
+ //
+ isAdmin Boolean @default(false)
}
model UserAccountBillingHistory {
@@ -1078,3 +1089,96 @@ model Kanban {
columns KanbanColumn[]
tasks KanbanTask[]
}
+
+// mapped to IEventReview
+model EventReview {
+ id String @id @default(uuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ //
+ name String
+ rating Float
+ comment String
+ helpful Float
+ avatarUrl String
+ postedAt DateTime @default(now()) // Assuming IDateValue maps to DateTime
+ isPurchased Boolean
+ attachments String[]
+ //
+ EventItem EventItem? @relation(fields: [eventItemId], references: [id])
+ eventItemId String?
+}
+
+// mapped to IEventItem
+model EventItem {
+ id String @id @default(uuid())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ //
+ sku String
+ name String
+ code String
+ price Float
+ taxes Float
+ tags String[]
+ sizes String[]
+ publish String
+ gender String[]
+ coverUrl String
+ images String[]
+ colors String[]
+ quantity Int
+ category String
+ available Int
+ totalSold Int
+ description String
+ totalRatings Float
+ totalReviews Int
+ inventoryType String
+ subDescription String
+ priceSale Float?
+ newLabel Json
+ saleLabel Json
+ ratings Json[]
+ //
+ eventDate DateTime @default(now())
+ joinMembers Json[]
+ title String
+ currency String
+ duration_m Float
+ ageBottom Float
+ ageTop Float
+ location String
+ avatar String[]
+ //
+ reviews EventReview[]
+}
+
+model AppLog {
+ id String @id @default(uuid())
+ timestamp DateTime @default(now())
+ //
+ level Int? @default(1)
+ message String? @default("")
+ //
+ module String?
+ userId String?
+ metadata Json?
+
+ @@index([timestamp])
+ @@index([level])
+ @@index([module])
+}
+
+model AccessLog {
+ id String @id @default(uuid())
+ timestamp DateTime @default(now())
+ //
+ userId String? @default("")
+ message String? @default("")
+ //
+ metadata Json?
+
+ @@index([timestamp])
+ @@index([userId])
+}
diff --git a/03_source/cms_backend/prisma/seed.ts b/03_source/cms_backend/prisma/seed.ts
index 263e2c5..f003585 100644
--- a/03_source/cms_backend/prisma/seed.ts
+++ b/03_source/cms_backend/prisma/seed.ts
@@ -25,6 +25,11 @@ import { FileStore } from './seeds/fileStore';
import { userItemSeed } from './seeds/userItem';
import { orderItemSeed } from './seeds/orderItem';
import { invoiceItemSeed } from './seeds/invoiceItem';
+//
+import { EventItemSeed } from './seeds/eventItem';
+import { EventReviewSeed } from './seeds/eventReview';
+import { appLogSeed } from './seeds/AppLog';
+import { accessLogSeed } from './seeds/AccessLog';
//
// import { Blog } from './seeds/blog';
@@ -44,8 +49,16 @@ import { invoiceItemSeed } from './seeds/invoiceItem';
await userItemSeed;
await orderItemSeed;
await invoiceItemSeed;
+ //
+ await EventReviewSeed;
+ await EventItemSeed;
+ //
+ await appLogSeed;
+ await accessLogSeed;
+
// await Blog;
// await Mail;
// await File;
// await Chat;
+ console.log('seed done');
})();
diff --git a/03_source/cms_backend/prisma/seeds/AccessLog.ts b/03_source/cms_backend/prisma/seeds/AccessLog.ts
new file mode 100644
index 0000000..9ff7e44
--- /dev/null
+++ b/03_source/cms_backend/prisma/seeds/AccessLog.ts
@@ -0,0 +1,36 @@
+import { PrismaClient } from '@prisma/client';
+import { format, parseISO } from 'date-fns';
+
+const L_ERROR = 0;
+const L_WARN = 1;
+const L_INFO = 2;
+const L_DEBUG = 3;
+const L_TRACE = 4;
+
+const prisma = new PrismaClient();
+
+async function accessLog() {
+ await prisma.accessLog.upsert({
+ where: { id: '0' },
+ update: {},
+ create: {
+ userId: '0',
+ message: 'helloworld',
+ metadata: { hello: 'world' },
+ },
+ });
+
+ console.log('seed AccessLog done');
+}
+
+const accessLogSeed = accessLog()
+ .then(async () => {
+ await prisma.$disconnect();
+ })
+ .catch(async (e) => {
+ console.error(e);
+ await prisma.$disconnect();
+ process.exit(1);
+ });
+
+export { accessLogSeed };
diff --git a/03_source/cms_backend/prisma/seeds/AppLog.ts b/03_source/cms_backend/prisma/seeds/AppLog.ts
new file mode 100644
index 0000000..4a281fe
--- /dev/null
+++ b/03_source/cms_backend/prisma/seeds/AppLog.ts
@@ -0,0 +1,71 @@
+import { PrismaClient } from '@prisma/client';
+import { format, parseISO } from 'date-fns';
+
+const L_ERROR = 0;
+const L_WARN = 1;
+const L_INFO = 2;
+const L_DEBUG = 3;
+const L_TRACE = 4;
+
+const prisma = new PrismaClient();
+
+async function appLog() {
+ await prisma.appLog.upsert({
+ where: { id: '0' },
+ update: {},
+ create: {
+ message: 'helloworld',
+ level: L_ERROR,
+ },
+ });
+
+ await prisma.appLog.upsert({
+ where: { id: '1' },
+ update: {},
+ create: {
+ message: 'warning message',
+ level: L_WARN,
+ },
+ });
+
+ await prisma.appLog.upsert({
+ where: { id: '2' },
+ update: {},
+ create: {
+ message: 'info message',
+ level: L_INFO,
+ },
+ });
+
+ await prisma.appLog.upsert({
+ where: { id: '3' },
+ update: {},
+ create: {
+ message: 'debug message',
+ level: L_DEBUG,
+ },
+ });
+
+ await prisma.appLog.upsert({
+ where: { id: '4' },
+ update: {},
+ create: {
+ message: 'trace message',
+ level: L_TRACE,
+ },
+ });
+
+ console.log('seed AppLog done');
+}
+
+const appLogSeed = appLog()
+ .then(async () => {
+ await prisma.$disconnect();
+ })
+ .catch(async (e) => {
+ console.error(e);
+ await prisma.$disconnect();
+ process.exit(1);
+ });
+
+export { appLogSeed };
diff --git a/03_source/cms_backend/prisma/seeds/Member.ts b/03_source/cms_backend/prisma/seeds/Member.ts
index 80bdb9a..a5a02be 100644
--- a/03_source/cms_backend/prisma/seeds/Member.ts
+++ b/03_source/cms_backend/prisma/seeds/Member.ts
@@ -2,12 +2,43 @@ import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function member() {
- for (let i = 0; i < 100; i++) {
+ [
+ {
+ email: 'tom@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1671656349322-41de944d259b`,
+ sex: 'M',
+ },
+ {
+ email: 'may@exampl.com',
+ avatar: `https://images.unsplash.com/photo-1522075469751-3a6694fb2f61`,
+ sex: 'F',
+ },
+ {
+ email: 'june@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1723867331866-e112500178a4`,
+ sex: 'M',
+ },
+ {
+ email: 'april@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1682089894837-e01e5cb8e471`,
+ sex: 'F',
+ },
+ {
+ email: 'susan@exampl.com',
+ avatar: `https://images.unsplash.com/photo-1485893086445-ed75865251e0`,
+ sex: 'M',
+ },
+ {
+ email: 'peter@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1722945763962-305a5a769cc8`,
+ sex: 'F',
+ },
+ ].forEach(async (m, i) => {
const john = await prisma.member.upsert({
- where: { email: `member${i}@example.com` },
+ where: { email: m.email },
update: {},
create: {
- email: `member${i}@example.com`,
+ email: m.email,
name: `member_${i}`,
age: 20 + i,
rank: i % 2 ? 'VIP' : 'NON_VIP',
@@ -25,11 +56,42 @@ async function member() {
self_introduction: 'Get me know me before you love me. Get me know me before you love me.',
music: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
pets: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
- character: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic']
- }
+ character: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
+ avatar: m.avatar,
+ sex: m.sex,
+ },
+ });
+ });
+
+ for (let i = 0; i < 3; i++) {
+ const john = await prisma.member.upsert({
+ where: { email: `member${i}@example.com` },
+ update: {},
+ create: {
+ email: `member${i}@example.com`,
+ name: `member_${i}`,
+ age: 20 + i,
+ rank: i % 2 ? 'VIP' : 'NON_VIP',
+ verified: i % 3 ? 'NOT_VERIFIED' : 'VERIFIED',
+ hobbies: ['fishing', 'basketball', 'piano'],
+ distance: `${40 + Math.random() * 40}km`,
+ location_area: 'Sai Kung',
+ greetings: 'Hi, I am ',
+ gender: 'man',
+ tall_cm: 172 + Math.random() * 10,
+ weight_kg: 60 + Math.random() * 50,
+ occupation: 'doctor',
+ language: ['English', 'French', 'Chinese'],
+ education: ['Degree of Computer'],
+ self_introduction: 'Get me know me before you love me. Get me know me before you love me.',
+ music: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
+ pets: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
+ character: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
+ avatar: '',
+ sex: i % 2 ? 'M' : 'F',
+ },
});
}
-
console.log('seed member done');
}
diff --git a/03_source/cms_backend/prisma/seeds/_mock.ts b/03_source/cms_backend/prisma/seeds/_mock.ts
index 2135e0f..0d9b3c9 100644
--- a/03_source/cms_backend/prisma/seeds/_mock.ts
+++ b/03_source/cms_backend/prisma/seeds/_mock.ts
@@ -78,11 +78,12 @@ export const _mock = {
avatar: (index: number) => `${CONFIG.basePath}/assets/images/avatar/avatar-${index + 1}.webp`,
travel: (index: number) => `${CONFIG.basePath}/assets/images/travel/travel-${index + 1}.webp`,
course: (index: number) => `${CONFIG.basePath}/assets/images/course/course-${index + 1}.webp`,
- company: (index: number) =>
- `${CONFIG.basePath}/assets/images/company/company-${index + 1}.webp`,
- product: (index: number) =>
- `${CONFIG.basePath}/assets/images/m-product/product-${index + 1}.webp`,
- portrait: (index: number) =>
- `${CONFIG.basePath}/assets/images/portrait/portrait-${index + 1}.webp`,
+ company: (index: number) => `${CONFIG.basePath}/assets/images/company/company-${index + 1}.webp`,
+ product: (index: number) => `${CONFIG.basePath}/assets/images/m-product/product-${index + 1}.webp`,
+ portrait: (index: number) => `${CONFIG.basePath}/assets/images/portrait/portrait-${index + 1}.webp`,
+ //
+ event: (index: number) => `${CONFIG.basePath}/assets/images/m-product/product-${index + 1}.webp`,
},
+ //
+ eventName: (index: number) => _eventNames[index],
};
diff --git a/03_source/cms_backend/prisma/seeds/eventItem.ts b/03_source/cms_backend/prisma/seeds/eventItem.ts
new file mode 100644
index 0000000..8d35668
--- /dev/null
+++ b/03_source/cms_backend/prisma/seeds/eventItem.ts
@@ -0,0 +1,277 @@
+import { _mock } from './_mock';
+import { _tags } from './assets';
+
+import { PrismaClient, EventReview } from '@prisma/client';
+import { _eventsReview } from './eventReview';
+import { CONFIG } from './global-config';
+
+import _ from 'lodash';
+
+const prisma = new PrismaClient();
+
+const COLORS = ['#FF4842', '#1890FF', '#FFC0CB', '#00AB55', '#FFC107', '#7F00FF', '#000000', '#FFFFFF'];
+
+const DESCRIPTION = `
+
Specifications
+
+
+
+ Category |
+ Mobile |
+
+
+ Manufacturer |
+ Apple |
+
+
+ Warranty |
+ 12 Months |
+
+
+ Serial number |
+ 358607726380311 |
+
+
+ Ships from |
+ United States |
+
+
+
+
+Event details
+
+ -
+
The foam sockliner feels soft and comfortable
+
+ -
+
Pull tab
+
+ -
+
Not intended for use as Personal Protective Equipment
+
+ -
+
Colour Shown: White/Black/Oxygen Purple/Action Grape
+
+ -
+
Style: 921826-109
+
+ -
+
Country/Region of Origin: China
+
+
+Benefits
+
+ -
+
Mesh and synthetic materials on the upper keep the fluid look of the OG while adding comfort
+ and durability.
+
+ -
+
Originally designed for performance running, the full-length Max Air unit adds soft, comfortable cushio
+ ning underfoot.
+
+ -
+
The foam midsole feels springy and soft.
+
+ -
+
The rubber outsole adds traction and durability.
+
+
+Delivery and returns
+Your order of $200 or more gets free standard delivery.
+
+Orders are processed and delivered Monday-Friday (excluding public holidays)
+
+`;
+
+const getColorSliceForIndex = (index: number) => {
+ if (index === 0) return COLORS.slice(0, 2);
+ if (index === 1) return COLORS.slice(1, 3);
+ if (index === 2) return COLORS.slice(2, 4);
+ if (index === 3) return COLORS.slice(3, 6);
+ if (index === 4 || index === 16 || index === 19) return COLORS.slice(4, 6);
+ if (index === 5 || index === 17) return COLORS.slice(5, 6);
+ if (index === 6 || index === 18) return COLORS.slice(0, 2);
+ if (index === 7) return COLORS.slice(4, 6);
+ if (index === 8) return COLORS.slice(2, 4);
+ if (index === 9 || index === 11) return COLORS.slice(2, 6);
+ if (index === 10) return COLORS.slice(3, 6);
+ if (index === 12) return COLORS.slice(2, 7);
+ if (index === 13) return COLORS.slice(4, 7);
+ if (index === 14) return COLORS.slice(0, 2);
+ if (index === 15) return COLORS.slice(5, 8);
+ return COLORS.slice(2, 6); // Default case
+};
+
+const generateAttachments = () => Array.from({ length: 20 }, (_, index) => _mock.image.event(index));
+
+const generateReviews = () => {
+ const attachments = generateAttachments();
+};
+
+const generateRatings = () =>
+ Array.from({ length: 5 }, (_, index) => ({
+ name: `${index + 1} Star`,
+ starCount: _mock.number.nativeL(index),
+ reviewCount: _mock.number.nativeL(index + 1),
+ }));
+
+const generateImages = () => Array.from({ length: 8 }, (_, index) => _mock.image.event(index));
+
+const _events = () =>
+ Array.from({ length: 2 }, (_, index) => {
+ const reviews = generateReviews();
+ const images = generateImages();
+ const ratings = generateRatings();
+ //
+ const publish = index % 3 ? 'published' : 'draft';
+
+ const category = (index % 2 && 'Shose') || (index % 3 && 'Apparel') || 'Accessories';
+
+ const gender = (index % 2 && ['Men']) || (index % 3 && ['Women', 'Kids']) || ['Kids'];
+
+ const available = (index % 2 && 72) || (index % 3 && 10) || 0;
+
+ const inventoryType = (index % 2 && 'in stock') || (index % 3 && 'low stock') || 'out of stock';
+
+ const priceSale = index % 3 ? undefined : _mock.number.price(index);
+
+ return {
+ id: _mock.id(index).toString(),
+ sku: `WW75K521${index}YW/SV`,
+ name: _mock.eventName(index),
+ gender,
+ images,
+ reviews,
+ publish,
+ ratings,
+ category,
+ available,
+ priceSale,
+ taxes: 10,
+ quantity: 80,
+ inventoryType,
+ tags: _tags.slice(0, 5),
+ code: `38BEE27${index}`,
+ description: DESCRIPTION,
+ createdAt: _mock.time(index),
+ price: _mock.number.price(index),
+ coverUrl: _mock.image.event(index),
+ colors: getColorSliceForIndex(index),
+ totalRatings: _mock.number.rating(index),
+ totalSold: _mock.number.nativeM(index + 1),
+ totalReviews: _mock.number.nativeL(index + 1),
+ newLabel: { enabled: [1, 2, 3].includes(index), content: 'NEW' },
+ saleLabel: { enabled: [4, 5].includes(index), content: 'SALE' },
+ sizes: ['6', '7', '8', '8.5', '9', '9.5', '10', '10.5', '11', '11.5', '12', '13'],
+ subDescription: 'Featuring the original ripple design inspired by Japanese bullet trains, the Nike Air Max 97 lets you push your style full-speed ahead.',
+ };
+ });
+
+const memberList = (num_of_member: number) =>
+ _.shuffle([
+ {
+ email: 'tom@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1671656349322-41de944d259b`,
+ sex: 'M',
+ },
+ {
+ email: 'may@exampl.com',
+ avatar: `https://images.unsplash.com/photo-1522075469751-3a6694fb2f61`,
+ sex: 'F',
+ },
+ {
+ email: 'june@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1723867331866-e112500178a4`,
+ sex: 'M',
+ },
+ {
+ email: 'april@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1682089894837-e01e5cb8e471`,
+ sex: 'F',
+ },
+ {
+ email: 'susan@exampl.com',
+ avatar: `https://images.unsplash.com/photo-1485893086445-ed75865251e0`,
+ sex: 'M',
+ },
+ {
+ email: 'peter@exampl.com',
+ avatar: `https://plus.unsplash.com/premium_photo-1722945763962-305a5a769cc8`,
+ sex: 'F',
+ },
+ ]).slice(0, num_of_member);
+
+async function eventItem() {
+ const temp_events = _events();
+
+ for (let i = 0; i < temp_events.length; i++) {
+ const temp_pr = _eventsReview();
+
+ const temp = await prisma.eventItem.upsert({
+ where: { id: temp_events[i].id.toString() },
+ update: {},
+ create: {
+ id: temp_events[i].id,
+ //
+ name: temp_events[i].name,
+ code: temp_events[i].code,
+ price: temp_events[i].price,
+ taxes: temp_events[i].taxes,
+ tags: temp_events[i].tags,
+ sizes: temp_events[i].sizes,
+ publish: temp_events[i].publish,
+ gender: temp_events[i].gender,
+ coverUrl: temp_events[i].coverUrl,
+ images: temp_events[i].images,
+ colors: temp_events[i].colors,
+ quantity: temp_events[i].quantity,
+ category: temp_events[i].category,
+ available: temp_events[i].available,
+ totalSold: temp_events[i].totalSold,
+ description: temp_events[i].description,
+ totalRatings: temp_events[i].totalRatings,
+ totalReviews: temp_events[i].totalReviews,
+ inventoryType: temp_events[i].inventoryType,
+ subDescription: temp_events[i].subDescription,
+ priceSale: temp_events[i].priceSale,
+ newLabel: temp_events[i].newLabel,
+ saleLabel: temp_events[i].saleLabel,
+ ratings: temp_events[i].ratings,
+ // review: { create: temp_pr },
+ reviews: { create: temp_pr },
+ sku: temp_events[i].sku,
+ //
+ eventDate: new Date(_mock.time(i)),
+ title: temp_events[i].name,
+ currency: 'HKD',
+ duration_m: 120,
+ ageBottom: 18,
+ ageTop: 60,
+ location: 'Hong Kong',
+ avatar: temp_events[i].images,
+ //
+ joinMembers: memberList(5 - i),
+ },
+ });
+ }
+ console.log('seed eventItem done');
+}
+
+const EventItem = eventItem()
+ .then(async () => {
+ await prisma.$disconnect();
+ })
+ .catch(async (e) => {
+ console.error(e);
+ await prisma.$disconnect();
+ process.exit(1);
+ });
+
+export { EventItem as EventItemSeed };
diff --git a/03_source/cms_backend/prisma/seeds/eventReview.ts b/03_source/cms_backend/prisma/seeds/eventReview.ts
new file mode 100644
index 0000000..3831cbf
--- /dev/null
+++ b/03_source/cms_backend/prisma/seeds/eventReview.ts
@@ -0,0 +1,61 @@
+import { _mock } from './_mock';
+import { _tags } from './assets';
+
+import { PrismaClient } from '@prisma/client';
+const prisma = new PrismaClient();
+
+const generateAttachments = () => Array.from({ length: 20 }, (_, index) => _mock.image.event(index));
+
+const generateReviews = () => {
+ const attachments = generateAttachments();
+
+ return Array.from({ length: 8 }, (_, index) => ({
+ // id: _mock.id(index),
+ name: _mock.fullName(index),
+ postedAt: _mock.time(index),
+ comment: _mock.sentence(index),
+ isPurchased: _mock.boolean(index),
+ rating: _mock.number.rating(index),
+ avatarUrl: _mock.image.avatar(index),
+ helpful: _mock.number.nativeL(index),
+ attachments: (index === 1 && attachments.slice(0, 1)) || (index === 3 && attachments.slice(2, 4)) || (index === 5 && attachments.slice(5, 8)) || [],
+ }));
+};
+
+export const _eventsReview = () => {
+ return generateReviews();
+};
+
+async function eventReview() {
+ const temp_pr = _eventsReview();
+
+ for (let i = 0; i < temp_pr.length; i++) {
+ const temp = await prisma.eventReview.upsert({
+ where: { id: i.toString() },
+ update: {},
+ create: {
+ name: temp_pr[i].name,
+ rating: temp_pr[i].rating,
+ comment: temp_pr[i].comment,
+ helpful: temp_pr[i].helpful,
+ avatarUrl: temp_pr[i].avatarUrl,
+ isPurchased: temp_pr[i].isPurchased,
+ attachments: temp_pr[i].attachments,
+ postedAt: temp_pr[i].postedAt,
+ },
+ });
+ }
+ console.log('seed eventReview done');
+}
+
+const EventReview = eventReview()
+ .then(async () => {
+ await prisma.$disconnect();
+ })
+ .catch(async (e) => {
+ console.error(e);
+ await prisma.$disconnect();
+ process.exit(1);
+ });
+
+export { EventReview as EventReviewSeed };
diff --git a/03_source/cms_backend/prisma/seeds/productReview.ts b/03_source/cms_backend/prisma/seeds/productReview.ts
index 89f845b..625a3fa 100644
--- a/03_source/cms_backend/prisma/seeds/productReview.ts
+++ b/03_source/cms_backend/prisma/seeds/productReview.ts
@@ -4,16 +4,7 @@ import { _tags } from './assets';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
-const COLORS = [
- '#FF4842',
- '#1890FF',
- '#FFC0CB',
- '#00AB55',
- '#FFC107',
- '#7F00FF',
- '#000000',
- '#FFFFFF'
-];
+const COLORS = ['#FF4842', '#1890FF', '#FFC0CB', '#00AB55', '#FFC107', '#7F00FF', '#000000', '#FFFFFF'];
const DESCRIPTION = `
Specifications
@@ -113,8 +104,7 @@ const getColorSliceForIndex = (index: number) => {
return COLORS.slice(2, 6); // Default case
};
-const generateAttachments = () =>
- Array.from({ length: 20 }, (_, index) => _mock.image.product(index));
+const generateAttachments = () => Array.from({ length: 20 }, (_, index) => _mock.image.product(index));
const generateReviews = () => {
const attachments = generateAttachments();
@@ -128,11 +118,7 @@ const generateReviews = () => {
rating: _mock.number.rating(index),
avatarUrl: _mock.image.avatar(index),
helpful: _mock.number.nativeL(index),
- attachments:
- (index === 1 && attachments.slice(0, 1)) ||
- (index === 3 && attachments.slice(2, 4)) ||
- (index === 5 && attachments.slice(5, 8)) ||
- []
+ attachments: (index === 1 && attachments.slice(0, 1)) || (index === 3 && attachments.slice(2, 4)) || (index === 5 && attachments.slice(5, 8)) || [],
}));
};
@@ -140,7 +126,7 @@ const generateRatings = () =>
Array.from({ length: 5 }, (_, index) => ({
name: `${index + 1} Star`,
starCount: _mock.number.nativeL(index),
- reviewCount: _mock.number.nativeL(index + 1)
+ reviewCount: _mock.number.nativeL(index + 1),
}));
const generateImages = () => Array.from({ length: 8 }, (_, index) => _mock.image.product(index));
@@ -164,8 +150,8 @@ async function productReview() {
avatarUrl: temp_pr[i].avatarUrl,
isPurchased: temp_pr[i].isPurchased,
attachments: temp_pr[i].attachments,
- postedAt: temp_pr[i].postedAt
- }
+ postedAt: temp_pr[i].postedAt,
+ },
});
}
console.log('seed productReview done');
diff --git a/03_source/cms_backend/prisma/seeds/superuser.ts b/03_source/cms_backend/prisma/seeds/superuser.ts
index 2bc3004..2415cc0 100644
--- a/03_source/cms_backend/prisma/seeds/superuser.ts
+++ b/03_source/cms_backend/prisma/seeds/superuser.ts
@@ -8,8 +8,9 @@ async function superuser() {
create: {
email: 'admin1@123.com',
name: 'Admin1',
- password: 'Aa12345678'
- }
+ password: 'Aa12345678',
+ admin: true,
+ },
});
// swagger test
@@ -19,8 +20,9 @@ async function superuser() {
create: {
email: 'fake@example.com',
name: 'swagger user',
- password: 'password1'
- }
+ password: 'password1',
+ admin: true,
+ },
});
console.log('seed superuser done');
diff --git a/03_source/cms_backend/prisma/seeds/user.ts b/03_source/cms_backend/prisma/seeds/user.ts
index d3f36a3..f155d18 100644
--- a/03_source/cms_backend/prisma/seeds/user.ts
+++ b/03_source/cms_backend/prisma/seeds/user.ts
@@ -9,16 +9,29 @@ async function user() {
email: 'alice@prisma.io',
name: 'Alice',
password: 'Aa12345678',
+ emailVerified: new Date(),
},
});
- const bob = await prisma.user.upsert({
+ await prisma.user.upsert({
+ where: { email: 'demo@minimals.cc' },
+ update: {},
+ create: {
+ email: 'demo@minimals.cc',
+ name: 'Demo',
+ password: '@2Minimal',
+ emailVerified: new Date(),
+ },
+ });
+
+ await prisma.user.upsert({
where: { email: 'bob@prisma.io' },
update: {},
create: {
email: 'bob@prisma.io',
name: 'Bob',
password: 'Aa12345678',
+ emailVerified: new Date(),
},
});
console.log('seed user done');
diff --git a/03_source/cms_backend/prisma/seeds/userItem.ts b/03_source/cms_backend/prisma/seeds/userItem.ts
index bd9a055..7cd013a 100644
--- a/03_source/cms_backend/prisma/seeds/userItem.ts
+++ b/03_source/cms_backend/prisma/seeds/userItem.ts
@@ -39,10 +39,12 @@ async function userItem() {
//
username: 'admin@123.com',
password: await generateHash('Aa1234567'),
+ //
+ isAdmin: true,
},
});
- for (let i = 1; i < 20; i++) {
+ for (let i = 1; i < 3; i++) {
const CJK_LOCALES = {
en: enFaker,
zh: zhFaker,
@@ -77,6 +79,8 @@ async function userItem() {
//
username: randomFaker.internet.username(),
password: await generateHash('Abc1234!'),
+ //
+ isAdmin: false,
},
});
}
diff --git a/03_source/cms_backend/src/_mock/_auth.ts b/03_source/cms_backend/src/_mock/_auth.ts
index 937aeec..be6c105 100644
--- a/03_source/cms_backend/src/_mock/_auth.ts
+++ b/03_source/cms_backend/src/_mock/_auth.ts
@@ -4,7 +4,7 @@ import { _mock } from 'src/_mock';
export const JWT_SECRET = 'minimal-secret-key';
-export const JWT_EXPIRES_IN = '3 days';
+export const JWT_EXPIRES_IN = '14 days';
export const _users = [
{
diff --git a/03_source/cms_backend/src/app/api/event/createEvent/route.ts b/03_source/cms_backend/src/app/api/event/createEvent/route.ts
new file mode 100644
index 0000000..2c3c800
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/createEvent/route.ts
@@ -0,0 +1,75 @@
+// src/app/api/product/createEvent/route.ts
+//
+// PURPOSE:
+// create product to db
+//
+// RULES:
+// T.B.A.
+//
+
+import type { NextRequest } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import prisma from '../../../lib/prisma';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * POST - Events
+ *************************************** */
+export async function POST(req: NextRequest) {
+ // logger('[Event] list', events.length);
+ const { data } = await req.json();
+ const createForm: CreateEventData = data as unknown as CreateEventData;
+
+ console.log({ createForm });
+
+ try {
+ console.log({ data });
+ await prisma.productItem.create({ data: createForm });
+ return response({ hello: 'world' }, STATUS.OK);
+ } catch (error) {
+ console.log({ hello: 'world', data });
+ return handleError('Event - Create', error);
+ }
+}
+
+type CreateEventData = {
+ // id: string;
+ sku: string;
+ name: string;
+ code: string;
+ price: number;
+ taxes: number;
+ tags: string[];
+ sizes: string[];
+ publish: string;
+ gender: string[];
+ coverUrl: string;
+ images: string[];
+ colors: string[];
+ quantity: number;
+ category: string;
+ available: number;
+ totalSold: number;
+ description: string;
+ totalRatings: number;
+ totalReviews: number;
+ inventoryType: string;
+ subDescription: string;
+ priceSale: number;
+ newLabel: {
+ content: string;
+ enabled: boolean;
+ };
+ saleLabel: {
+ content: string;
+ enabled: boolean;
+ };
+ // ratings: {
+ // name: string;
+ // starCount: number;
+ // reviewCount: number;
+ // }[];
+};
diff --git a/03_source/cms_backend/src/app/api/event/deleteEvent/route.ts b/03_source/cms_backend/src/app/api/event/deleteEvent/route.ts
new file mode 100644
index 0000000..0695087
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/deleteEvent/route.ts
@@ -0,0 +1,44 @@
+// src/app/api/event/deleteEvent/route.ts
+//
+// PURPOSE:
+// delete 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 prisma from '../../../lib/prisma';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * handle Delete Events
+ *************************************** */
+export async function DELETE(req: NextRequest) {
+ 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 prisma.eventItem.delete({ where: { id: eventId } });
+
+ if (!event) {
+ return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
+ }
+
+ logger('[Event] details', event.id);
+
+ return response({ event }, STATUS.OK);
+ } catch (error) {
+ return handleError('Event - Get details', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/event/deleteEvent/test.http b/03_source/cms_backend/src/app/api/event/deleteEvent/test.http
new file mode 100644
index 0000000..c88c093
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/deleteEvent/test.http
@@ -0,0 +1,3 @@
+###
+
+DELETE http://localhost:7272/api/event/deleteEvent?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b06
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
new file mode 100644
index 0000000..be0e574
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/details/route.ts
@@ -0,0 +1,47 @@
+// src/app/api/event/details/route.ts
+//
+// PURPOSE:
+// save event to 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 prisma from '../../../lib/prisma';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * GET Event detail
+ *************************************** */
+export async function GET(req: NextRequest) {
+ 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 prisma.eventItem.findFirst({
+ include: { reviews: true },
+ where: { id: eventId },
+ });
+
+ if (!event) {
+ return response({ message: 'Event not found!' }, STATUS.NOT_FOUND);
+ }
+
+ logger('[Event] details', event.id);
+
+ return response({ event }, STATUS.OK);
+ } catch (error) {
+ 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
new file mode 100644
index 0000000..9df1c50
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/details/test.http
@@ -0,0 +1,3 @@
+###
+
+GET http://localhost:7272/api/event/details?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
diff --git a/03_source/cms_backend/src/app/api/event/image/upload/route.ts b/03_source/cms_backend/src/app/api/event/image/upload/route.ts
new file mode 100644
index 0000000..a7f3d72
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/image/upload/route.ts
@@ -0,0 +1,30 @@
+// src/app/api/event/image/upload/route.ts
+//
+// PURPOSE:
+// handle upload event image
+//
+// RULES:
+// T.B.A.
+
+import type { NextRequest } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+// import prisma from '../../../lib/prisma';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * GET - Events
+ *************************************** */
+export async function POST(req: NextRequest) {
+ try {
+ const { data } = await req.json();
+ console.log('helloworld');
+
+ return response({ hello: 'world' }, STATUS.OK);
+ } catch (error) {
+ console.log({ hello: 'world' });
+ return handleError('Event - store event image', error);
+ }
+}
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
new file mode 100644
index 0000000..a5784a9
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/list/route.ts
@@ -0,0 +1,22 @@
+// 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';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * GET - Events
+ *************************************** */
+export async function GET() {
+ try {
+ const events = await prisma.eventItem.findMany();
+
+ logger('[Event] list', events.length);
+
+ return response({ events }, STATUS.OK);
+ } catch (error) {
+ return handleError('Event - Get list', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/event/list/test.http b/03_source/cms_backend/src/app/api/event/list/test.http
new file mode 100644
index 0000000..b82a74f
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/list/test.http
@@ -0,0 +1,3 @@
+###
+
+GET http://localhost:7272/api/event/list
diff --git a/03_source/cms_backend/src/app/api/event/saveEvent/route.ts b/03_source/cms_backend/src/app/api/event/saveEvent/route.ts
new file mode 100644
index 0000000..cda23c2
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/saveEvent/route.ts
@@ -0,0 +1,129 @@
+// src/app/api/event/saveEvent/route.ts
+//
+// PURPOSE:
+// save event 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';
+
+// ----------------------------------------------------------------------
+
+/** **************************************
+ * GET - Events
+ *************************************** */
+export async function POST(req: NextRequest) {
+ // logger('[Event] list', events.length);
+ const { data } = await req.json();
+
+ try {
+ const events = await prisma.eventItem.updateMany({
+ data: {
+ name: data.name,
+ sku: data.sku,
+ code: data.code,
+ price: data.price,
+ taxes: data.taxes,
+ tags: data.tags,
+ sizes: data.sizes,
+ publish: data.publish,
+ gender: data.gender,
+ coverUrl: data.coverUrl,
+ images: data.images,
+ colors: data.colors,
+ quantity: data.quantity,
+ category: data.category,
+ available: data.available,
+ totalSold: data.totalSold,
+ description: data.description,
+ totalRatings: data.totalRatings,
+ totalReviews: data.totalReviews,
+ inventoryType: data.inventoryType,
+ subDescription: data.subDescription,
+ priceSale: data.priceSale,
+ //
+ newLabel: {
+ content: data.newLabel?.content || '',
+ enabled: data.newLabel?.enabled ?? false,
+ },
+ saleLabel: {
+ content: data.saleLabel?.content || '',
+ enabled: data.saleLabel?.enabled ?? false,
+ },
+ ratings: {
+ set: data.ratings.map((rating: { name: string; starCount: number; reviewCount: number }) => ({
+ name: rating.name,
+ starCount: rating.starCount,
+ reviewCount: rating.reviewCount,
+ })),
+ },
+ },
+ where: { id: data.id },
+ });
+
+ return response({ data }, STATUS.OK);
+ } catch (error) {
+ console.log({ data });
+ return handleError('Event - Get list', error);
+ }
+}
+
+export type IEventItem = {
+ id: string;
+ sku: string;
+ name: string;
+ code: string;
+ price: number;
+ taxes: number;
+ tags: string[];
+ sizes: string[];
+ publish: string;
+ gender: string[];
+ coverUrl: string;
+ images: string[];
+ colors: string[];
+ quantity: number;
+ category: string;
+ available: number;
+ totalSold: number;
+ description: string;
+ totalRatings: number;
+ totalReviews: number;
+ // createdAt: IDateValue;
+ inventoryType: string;
+ subDescription: string;
+ priceSale: number | null;
+ // reviews: IEventReview[];
+ newLabel: {
+ content: string;
+ enabled: boolean;
+ };
+ saleLabel: {
+ content: string;
+ enabled: boolean;
+ };
+ ratings: {
+ name: string;
+ starCount: number;
+ reviewCount: number;
+ }[];
+};
+
+export type IDateValue = string | number | null;
+
+export type IEventReview = {
+ id: string;
+ name: string;
+ rating: number;
+ comment: string;
+ helpful: number;
+ avatarUrl: string;
+ postedAt: IDateValue;
+ isPurchased: boolean;
+ attachments?: string[];
+};
diff --git a/03_source/cms_backend/src/app/api/event/search/route.ts b/03_source/cms_backend/src/app/api/event/search/route.ts
new file mode 100644
index 0000000..af5ccdf
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/event/search/route.ts
@@ -0,0 +1,35 @@
+import type { NextRequest } from 'next/server';
+
+import { logger } from 'src/utils/logger';
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { _events } from 'src/_mock/_event';
+
+// ----------------------------------------------------------------------
+
+export const runtime = 'edge';
+
+/** **************************************
+ * GET - Search events
+ *************************************** */
+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 events = _events();
+
+ // Accept search by name or sku
+ const results = events.filter(({ name, sku }) => name.toLowerCase().includes(query) || sku?.toLowerCase().includes(query));
+
+ logger('[Event] search-results', results.length);
+
+ return response({ results }, STATUS.OK);
+ } catch (error) {
+ return handleError('Event - Get search', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/helloworld/_GUIDELINES.md b/03_source/cms_backend/src/app/api/helloworld/_GUIDELINES.md
index 199ab1c..5695938 100644
--- a/03_source/cms_backend/src/app/api/helloworld/_GUIDELINES.md
+++ b/03_source/cms_backend/src/app/api/helloworld/_GUIDELINES.md
@@ -1,7 +1,8 @@
# GUIDELINE
-this is a helloworld api endpoint
-this is a demo to handle helloworld record
+- 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`
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
new file mode 100644
index 0000000..aad3cef
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/helloworld/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/helloworld/detail/test.http b/03_source/cms_backend/src/app/api/helloworld/detail/test.http
new file mode 100644
index 0000000..60c39cc
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/helloworld/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/helloworld/route.ts b/03_source/cms_backend/src/app/api/helloworld/route.ts
index b8a5daf..6b46568 100644
--- a/03_source/cms_backend/src/app/api/helloworld/route.ts
+++ b/03_source/cms_backend/src/app/api/helloworld/route.ts
@@ -6,6 +6,11 @@ import { listHelloworlds, deleteHelloworld, updateHelloworld, createNewHelloworl
// import prisma from '../../lib/prisma';
+/**
+ ***************************************
+ * GET - list all Helloworlds
+ ***************************************
+ */
export async function GET(req: NextRequest, res: NextResponse) {
try {
const result = await listHelloworlds();
diff --git a/03_source/cms_backend/src/app/api/order/_GUIDELINES.md b/03_source/cms_backend/src/app/api/order/_GUIDELINES.md
new file mode 100644
index 0000000..5695938
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/order/_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/order/route.ts b/03_source/cms_backend/src/app/api/order/route.ts
new file mode 100644
index 0000000..190381c
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/order/route.ts
@@ -0,0 +1,76 @@
+import type { NextRequest, NextResponse } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { listOrders, deleteOrder, updateOrder, createOrder } from 'src/app/services/order.service';
+
+/**
+ **************************************
+ * GET - Order
+ ***************************************
+ */
+export async function GET(req: NextRequest, res: NextResponse) {
+ try {
+ const orders = await listOrders();
+ return response(orders, STATUS.OK);
+ } catch (error) {
+ return handleError('Order - Get list', error);
+ }
+}
+
+/**
+ ***************************************
+ * POST - Create Order
+ ***************************************
+ */
+export async function POST(req: NextRequest) {
+ const { data } = await req.json();
+
+ try {
+ const order = await createOrder(data);
+ return response(order, STATUS.CREATED);
+ } catch (error) {
+ return handleError('Order - Create', error);
+ }
+}
+
+/**
+ ***************************************
+ * PUT - Update Order
+ ***************************************
+ */
+export async function PUT(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const orderId = searchParams.get('orderId');
+ const { data } = await req.json();
+
+ try {
+ if (!orderId) throw new Error('orderId cannot be null');
+ const id: number = parseInt(orderId);
+
+ const updatedOrder = await updateOrder(id, data);
+ return response(updatedOrder, STATUS.OK);
+ } catch (error) {
+ return handleError('Order - Update', error);
+ }
+}
+
+/**
+ ***************************************
+ * DELETE - Delete Order
+ ***************************************
+ */
+export async function DELETE(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const orderId = searchParams.get('orderId');
+
+ try {
+ if (!orderId) throw new Error('orderId cannot be null');
+ const id: number = parseInt(orderId);
+
+ await deleteOrder(id);
+ return response({ success: true }, STATUS.OK);
+ } catch (error) {
+ return handleError('Order - Delete', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/order/test.http b/03_source/cms_backend/src/app/api/order/test.http
new file mode 100644
index 0000000..8c89459
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/order/test.http
@@ -0,0 +1,32 @@
+###
+GET http://localhost:7272/api/order
+
+###
+GET http://localhost:7272/api/order?orderId=1
+
+###
+POST http://localhost:7272/api/order
+content-type: application/json
+
+{
+ "data": {
+ "product": "Sample Product",
+ "quantity": 1,
+ "price": 99.99
+ }
+}
+
+###
+PUT http://localhost:7272/api/order?orderId=1
+content-type: application/json
+
+{
+ "data": {
+ "product": "Updated Product",
+ "quantity": 2,
+ "price": 199.98
+ }
+}
+
+###
+DELETE http://localhost:7272/api/order?orderId=1
diff --git a/03_source/cms_backend/src/app/api/product/route.ts b/03_source/cms_backend/src/app/api/product/route.ts
new file mode 100644
index 0000000..d30e80b
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/product/route.ts
@@ -0,0 +1,74 @@
+import type { NextRequest, NextResponse } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { listProducts, deleteProduct, updateProduct, createProduct } from 'src/app/services/product.service';
+
+/**
+ **************************************
+ * GET - Product
+ ***************************************
+ */
+export async function GET(req: NextRequest, res: NextResponse) {
+ try {
+ const products = await listProducts();
+ return response(products, STATUS.OK);
+ } catch (error) {
+ return handleError('Product - Get list', error);
+ }
+}
+
+/**
+ ***************************************
+ * POST - Create Product
+ ***************************************
+ */
+export async function POST(req: NextRequest) {
+ const { data } = await req.json();
+
+ try {
+ const product = await createProduct(data);
+ return response(product, STATUS.CREATED);
+ } catch (error) {
+ return handleError('Product - Create', error);
+ }
+}
+
+/**
+ ***************************************
+ * PUT - Update Product
+ ***************************************
+ */
+export async function PUT(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const productId = searchParams.get('productId');
+ const { data } = await req.json();
+
+ try {
+ if (!productId) throw new Error('productId cannot be null');
+
+ const result = await updateProduct(productId, data);
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('Product - Update', error);
+ }
+}
+
+/**
+ ***************************************
+ * DELETE - Delete Product
+ ***************************************
+ */
+export async function DELETE(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const productId = searchParams.get('productId');
+
+ try {
+ if (!productId) throw new Error('productId cannot be null');
+
+ await deleteProduct(productId);
+ return response({ success: true }, STATUS.OK);
+ } catch (error) {
+ return handleError('Product - Delete', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/product/test.http b/03_source/cms_backend/src/app/api/product/test.http
new file mode 100644
index 0000000..221b429
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/product/test.http
@@ -0,0 +1,33 @@
+###
+GET http://localhost:7272/api/product
+
+###
+GET http://localhost:7272/api/product?productId=1
+
+###
+POST http://localhost:7272/api/product
+content-type: application/json
+
+{
+ "data": {
+ "sku": "PROD-001",
+ "name": "Premium Product",
+ "price": 99.99,
+ "quantity": 100,
+ "category": "Electronics",
+ }
+}
+
+###
+PUT http://localhost:7272/api/product?productId=1
+content-type: application/json
+
+{
+ "data": {
+ "price": 89.99,
+ "quantity": 95
+ }
+}
+
+###
+DELETE http://localhost:7272/api/product?productId=1
diff --git a/03_source/cms_backend/src/app/api/user/changeToAdmin/route.ts b/03_source/cms_backend/src/app/api/user/changeToAdmin/route.ts
new file mode 100644
index 0000000..7157583
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/changeToAdmin/route.ts
@@ -0,0 +1,36 @@
+// src/app/api/user/promoteToAdmin/route.ts
+//
+import type { NextRequest } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { changeToAdmin } from 'src/app/services/userItem.service';
+
+/**
+ ***************************************
+ * PUT - promote user to admin
+ ***************************************
+ */
+export async function PUT(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+
+ // userId, the userId going to be admin
+ // {data: requestUserId}, the userId sending request to promote admin
+ // the requestUserId should be a admin admin already
+
+ const userId = searchParams.get('userId');
+ const {
+ data: { requestUserId },
+ } = await req.json();
+
+ try {
+ if (!userId) return response('userId cannot be null', STATUS.BAD_REQUEST);
+ if (!requestUserId) return response('requestUserId cannot be null', STATUS.BAD_REQUEST);
+
+ const result = await changeToAdmin(userId, requestUserId);
+
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('promote to admin', JSON.stringify(error));
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/user/changeToAdmin/test.http b/03_source/cms_backend/src/app/api/user/changeToAdmin/test.http
new file mode 100644
index 0000000..16a83f6
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/changeToAdmin/test.http
@@ -0,0 +1,33 @@
+# step 1 check not a admin
+###
+GET http://localhost:7272/api/user/details?userId=00b8b53b-dfe6-4b07-8d23-0dc9f75e7a2c
+
+# step 1 check not a admin
+###
+GET http://localhost:7272/api/user/details?userId=5e072d02-919c-49c4-98b4-9659b8eff231
+
+
+# step 2 request to change admin
+###
+PUT http://localhost:7272/api/user/changeToAdmin?userId=00b8b53b-dfe6-4b07-8d23-0dc9f75e7a2c
+content-type: application/json
+
+{
+ "data": {"requestUserId": "5e072d02-919c-49c4-98b4-9659b8eff231"}
+}
+
+
+
+# step 2 request to change user
+###
+PUT http://localhost:7272/api/user/changeToUser?userId=00b8b53b-dfe6-4b07-8d23-0dc9f75e7a2c
+content-type: application/json
+
+{
+ "data": {"requestUserId": "5e072d02-919c-49c4-98b4-9659b8eff231"}
+}
+
+
+# step 3 it is now admin
+###
+GET http://localhost:7272/api/user/details?userId=00b8b53b-dfe6-4b07-8d23-0dc9f75e7a2c
diff --git a/03_source/cms_backend/src/app/api/user/changeToUser/route.ts b/03_source/cms_backend/src/app/api/user/changeToUser/route.ts
new file mode 100644
index 0000000..4191a4d
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/changeToUser/route.ts
@@ -0,0 +1,36 @@
+// src/app/api/user/changeToUser/route.ts
+//
+import type { NextRequest } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { changeToUser } from 'src/app/services/userItem.service';
+
+/**
+ ***************************************
+ * PUT - change admin back to regular user
+ ***************************************
+ */
+export async function PUT(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+
+ // userId, the userId going to be changed to regular user
+ // {data: requestUserId}, the userId sending request to change role
+ // the requestUserId should be an admin
+
+ const userId = searchParams.get('userId');
+ const {
+ data: { requestUserId },
+ } = await req.json();
+
+ try {
+ if (!userId) throw new Error('userId cannot be null');
+ if (!requestUserId) throw new Error('requestUserId cannot be null');
+
+ const result = await changeToUser(userId, requestUserId);
+
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('change to regular user', JSON.stringify(error));
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/user/changeToUser/test.http b/03_source/cms_backend/src/app/api/user/changeToUser/test.http
new file mode 100644
index 0000000..b43a24a
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/changeToUser/test.http
@@ -0,0 +1,17 @@
+###
+PUT http://localhost:7272/api/user/changeToUser?userId=cmbfx38e30000ssek6ufjewcp
+content-type: application/json
+
+{
+ "data": {"requestUserId": "cmbfx38kk000esseka9qo8lpt"}
+}
+
+
+###
+GET http://localhost:7272/api/user/changeToUser?userId=
+
+###
+GET http://localhost:7272/api/user
+
+###
+GET http://localhost:7272/api/user?userId=cmbfx38e30000ssek6ufjewcp
diff --git a/03_source/cms_backend/src/app/api/user/checkAdmin/route.ts b/03_source/cms_backend/src/app/api/user/checkAdmin/route.ts
new file mode 100644
index 0000000..85f2557
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/checkAdmin/route.ts
@@ -0,0 +1,21 @@
+import type { NextRequest, NextResponse } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { isAdmin } from 'src/app/services/userItem.service';
+
+// import prisma from '../../lib/prisma';
+
+export async function GET(req: NextRequest, res: NextResponse) {
+ const { searchParams } = req.nextUrl;
+ const userId = searchParams.get('userId');
+
+ try {
+ if (!userId) throw new Error('userId cannot be null');
+ const result = await isAdmin(userId);
+
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('Post - Get latest', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/user/checkAdmin/test.http b/03_source/cms_backend/src/app/api/user/checkAdmin/test.http
new file mode 100644
index 0000000..eb4600a
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/checkAdmin/test.http
@@ -0,0 +1,9 @@
+###
+GET http://localhost:7272/api/user/checkAdmin?userId=cmbfx38ey0001ssek5pr05o70
+
+###
+GET http://localhost:7272/api/user/checkAdmin?userId=cmbfvonkx000411ykuah8pp8s
+
+
+###
+GET http://localhost:7272/api/user/checkAdmin
diff --git a/03_source/cms_backend/src/app/api/user/details/route.ts b/03_source/cms_backend/src/app/api/user/details/route.ts
index a1165ec..5455efd 100644
--- a/03_source/cms_backend/src/app/api/user/details/route.ts
+++ b/03_source/cms_backend/src/app/api/user/details/route.ts
@@ -1,4 +1,4 @@
-// src/app/api/product/details/route.ts
+// src/app/api/user/details/route.ts
//
// PURPOSE:
// read user from db by id
@@ -24,9 +24,7 @@ export async function GET(req: NextRequest) {
// RULES: userId must exist
const userId = searchParams.get('userId');
- if (!userId) {
- return response({ message: 'userId is required!' }, STATUS.BAD_REQUEST);
- }
+ if (!userId) return response({ message: 'userId is required!' }, STATUS.BAD_REQUEST);
// NOTE: userId confirmed exist, run below
const user = await prisma.userItem.findFirst({
@@ -34,9 +32,7 @@ export async function GET(req: NextRequest) {
where: { id: userId },
});
- if (!user) {
- return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
- }
+ if (!user) return response({ message: 'User not found!' }, STATUS.NOT_FOUND);
logger('[User] details', user.id);
diff --git a/03_source/cms_backend/src/app/api/user/helloworld/route.ts b/03_source/cms_backend/src/app/api/user/helloworld/route.ts
new file mode 100644
index 0000000..85f2557
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/helloworld/route.ts
@@ -0,0 +1,21 @@
+import type { NextRequest, NextResponse } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { isAdmin } from 'src/app/services/userItem.service';
+
+// import prisma from '../../lib/prisma';
+
+export async function GET(req: NextRequest, res: NextResponse) {
+ const { searchParams } = req.nextUrl;
+ const userId = searchParams.get('userId');
+
+ try {
+ if (!userId) throw new Error('userId cannot be null');
+ const result = await isAdmin(userId);
+
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('Post - Get latest', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/user/helloworld/test.http b/03_source/cms_backend/src/app/api/user/helloworld/test.http
new file mode 100644
index 0000000..341bca1
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/helloworld/test.http
@@ -0,0 +1,2 @@
+###
+GET http://localhost:7272/api/user
diff --git a/03_source/cms_backend/src/app/api/user/list/route.ts b/03_source/cms_backend/src/app/api/user/list/route.ts
index 097f217..9324e74 100644
--- a/03_source/cms_backend/src/app/api/user/list/route.ts
+++ b/03_source/cms_backend/src/app/api/user/list/route.ts
@@ -1,17 +1,20 @@
// src/app/api/product/list/route.ts
+//
import { logger } from 'src/utils/logger';
import { STATUS, response, handleError } from 'src/utils/response';
-import prisma from '../../../lib/prisma';
+import { listUsers } from 'src/app/services/userItem.service';
// ----------------------------------------------------------------------
-/** **************************************
+/**
+ ***************************************
* GET - Products
- *************************************** */
+ ***************************************
+ */
export async function GET() {
try {
- const users = await prisma.userItem.findMany();
+ const users = await listUsers();
logger('[User] list', users.length);
diff --git a/03_source/cms_backend/src/app/api/user/route.ts b/03_source/cms_backend/src/app/api/user/route.ts
new file mode 100644
index 0000000..9a151a0
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/route.ts
@@ -0,0 +1,80 @@
+import type { NextRequest, NextResponse } from 'next/server';
+
+import { STATUS, response, handleError } from 'src/utils/response';
+
+import { listUsers, deleteUser, updateUser, createNewUser } from 'src/app/services/userItem.service';
+
+// import prisma from '../../lib/prisma';
+
+export async function GET(req: NextRequest, res: NextResponse) {
+ try {
+ const result = await listUsers();
+
+ return response(result, STATUS.OK);
+ } catch (error) {
+ return handleError('User - Get latest', error);
+ }
+}
+
+/**
+ ***************************************
+ * POST - create User
+ ***************************************
+ */
+export async function POST(req: NextRequest) {
+ const { data } = await req.json();
+
+ try {
+ const createResult = await createNewUser(data);
+
+ return response(createResult, STATUS.OK);
+ } catch (error) {
+ return handleError('User - Create', error);
+ }
+}
+
+/**
+ ***************************************
+ * PUT - update User
+ ***************************************
+ */
+export async function PUT(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const userId = searchParams.get('userId');
+
+ const { data } = await req.json();
+
+ try {
+ if (!userId) throw new Error('userId cannot null');
+ const id: number = parseInt(userId);
+
+ const updateResult = await updateUser(id, data);
+
+ return response(updateResult, STATUS.OK);
+ } catch (error) {
+ return handleError('User - Update', error);
+ }
+}
+
+/**
+ ***************************************
+ * DELETE - update User
+ ***************************************
+ */
+export async function DELETE(req: NextRequest) {
+ const { searchParams } = req.nextUrl;
+ const userId = searchParams.get('userId');
+
+ const { data } = await req.json();
+
+ try {
+ if (!userId) throw new Error('userId cannot null');
+ const id: number = parseInt(userId);
+
+ const deleteResult = await deleteUser(id);
+
+ return response(deleteResult, STATUS.OK);
+ } catch (error) {
+ return handleError('User - Update', error);
+ }
+}
diff --git a/03_source/cms_backend/src/app/api/user/test.http b/03_source/cms_backend/src/app/api/user/test.http
new file mode 100644
index 0000000..9a1598f
--- /dev/null
+++ b/03_source/cms_backend/src/app/api/user/test.http
@@ -0,0 +1,26 @@
+###
+GET http://localhost:7272/api/user
+
+###
+GET http://localhost:7272/api/user?userId=cmbfvonhl000011ykdi345yc9
+
+###
+POST http://localhost:7272/api/user?userId=1
+content-type: application/json
+
+{
+ "data":{"name": "John Doe"}
+}
+
+###
+PUT http://localhost:7272/api/user?userId=1
+content-type: application/json
+
+{
+ "data": {"name": "John Doe"}
+}
+
+
+###
+DELETE http://localhost:7272/api/user?userId=1
+
diff --git a/03_source/cms_backend/src/app/services/AccessLog.service.ts b/03_source/cms_backend/src/app/services/AccessLog.service.ts
new file mode 100644
index 0000000..a936a9d
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/AccessLog.service.ts
@@ -0,0 +1,66 @@
+// src/app/services/AccessLog.service.ts
+//
+// PURPOSE:
+// Service for handling AccessLog records
+//
+// RULES:
+// - All methods return Promises
+// - Input validation should be done at controller level
+// - Errors should be propagated to caller
+
+import type { AccessLog } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+// type CreateAccessLog = {
+// userId?: string;
+// message?: string;
+// metadata?: Record;
+// };
+
+// type UpdateAccessLog = {
+// status?: number;
+// metadata?: object;
+// };
+
+async function listAccessLogs(): Promise {
+ return prisma.accessLog.findMany({
+ orderBy: { timestamp: 'desc' },
+ take: 100,
+ });
+}
+
+async function getAccessLog(id: string): Promise {
+ return prisma.accessLog.findUnique({ where: { id } });
+}
+
+async function createAccessLog(userId?: string, message?: string, metadata?: Record): Promise {
+ return prisma.accessLog.create({
+ data: {
+ userId,
+ message,
+ metadata,
+ },
+ });
+}
+
+// async function update(id: string, data: UpdateAccessLog): Promise {
+// return prisma.accessLog.update({
+// where: { id },
+// data: {
+// ...data,
+// metadata: data.metadata || {},
+// },
+// });
+// }
+
+// async function deleteAccessLog(id: string): Promise {
+// return prisma.accessLog.delete({ where: { id } });
+// }
+
+export {
+ //
+ getAccessLog,
+ listAccessLogs,
+ createAccessLog,
+};
diff --git a/03_source/cms_backend/src/app/services/AppLog.service.ts b/03_source/cms_backend/src/app/services/AppLog.service.ts
new file mode 100644
index 0000000..abc6956
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/AppLog.service.ts
@@ -0,0 +1,52 @@
+// src/app/services/AppLog.service.ts
+//
+// REQ0047/T.B.A.
+//
+// PURPOSE:
+// - AppLog example for handling AppLog Record
+//
+// RULES:
+// - T.B.A.
+//
+
+import type { AppLog } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+type CreateAppLog = {
+ level: number;
+ message: string;
+};
+
+// type UpdateAppLog = {
+// level: number;
+// message: string;
+// };
+
+async function listAppLogs(): Promise {
+ return prisma.appLog.findMany();
+}
+
+async function getAppLog(appLogId: string) {
+ return prisma.appLog.findFirst({ where: { id: appLogId } });
+}
+
+async function createNewAppLog(createForm: CreateAppLog) {
+ return prisma.appLog.create({ data: createForm });
+}
+
+// async function updateAppLog(appLogId: string, updateForm: UpdateAppLog) {
+// return prisma.appLog.update({ where: { id: appLogId }, data: updateForm });
+// }
+
+async function deleteAppLog(appLogId: string) {
+ return prisma.appLog.delete({ where: { id: appLogId } });
+}
+
+export {
+ getAppLog,
+ listAppLogs,
+ // updateAppLog,
+ deleteAppLog,
+ createNewAppLog,
+};
diff --git a/03_source/cms_backend/src/app/services/_PROMPTS/clone_srevice.md b/03_source/cms_backend/src/app/services/_PROMPTS/clone_srevice.md
new file mode 100644
index 0000000..c0f6ead
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/_PROMPTS/clone_srevice.md
@@ -0,0 +1,7 @@
+with knowledge in schema.prisma file,
+please refer the below helloworld example `helloworld.service.ts`
+and create `user.service.ts` to cover user record
+
+thanks
+
+`/home/logic/_wsl_workspace/001_github_ws/HKSingleParty-ws/HKSingleParty/03_source/cms_backend/src/app/services/helloworld.service.ts`
diff --git a/03_source/cms_backend/src/app/services/event.service.ts b/03_source/cms_backend/src/app/services/event.service.ts
new file mode 100644
index 0000000..8c22679
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/event.service.ts
@@ -0,0 +1,65 @@
+// src/app/services/event.service.ts
+//
+// PURPOSE:
+// - Service for handling Event records
+//
+// RULES:
+// - Follows same pattern as helloworld.service.ts
+//
+
+import type { Event } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+type CreateEvent = {
+ eventDate: DateTime;
+ title: string;
+ joinMembers?: Json[];
+ price: number;
+ currency: string;
+ duration_m: number;
+ ageBottom: number;
+ ageTop: number;
+ location: string;
+ avatar?: string;
+ memberId?: number;
+};
+
+type UpdateEvent = {
+ eventDate?: DateTime;
+ title?: string;
+ joinMembers?: Json[];
+ price?: number;
+ currency?: string;
+ duration_m?: number;
+ ageBottom?: number;
+ ageTop?: number;
+ location?: string;
+ avatar?: string;
+ memberId?: number;
+};
+
+async function listEvents(): Promise {
+ return prisma.event.findMany();
+}
+
+async function getEvent(eventId: number) {
+ return prisma.event.findFirst({ where: { id: eventId } });
+}
+
+async function createNewEvent(createForm: CreateEvent) {
+ return prisma.event.create({ data: createForm });
+}
+
+async function updateEvent(eventId: number, updateForm: UpdateEvent) {
+ return prisma.event.update({
+ where: { id: eventId },
+ data: updateForm,
+ });
+}
+
+async function deleteEvent(eventId: number) {
+ return prisma.event.delete({ where: { id: eventId } });
+}
+
+export { getEvent, listEvents, updateEvent, deleteEvent, createNewEvent };
diff --git a/03_source/cms_backend/src/app/services/order.service.ts b/03_source/cms_backend/src/app/services/order.service.ts
new file mode 100644
index 0000000..2d8c9f6
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/order.service.ts
@@ -0,0 +1,70 @@
+// src/app/services/order.service.ts
+//
+// PURPOSE:
+// - Handle Order Record CRUD operations
+// - Manage order status transitions
+// - Handle order-event relationships
+//
+
+import type { OrderItem } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+type CreateOrderItem = {
+ orderNumber?: string;
+ // status?: string;
+ // eventIds?: number[];
+};
+
+type UpdateOrderItem = {
+ orderNumber?: string;
+ // status?: string;
+ // last_payment_date?: Date;
+ // eventIds?: number[];
+};
+
+async function listOrders(): Promise {
+ return prisma.orderItem.findMany();
+}
+
+async function getOrder(orderId: string): Promise {
+ return prisma.orderItem.findFirst({
+ where: { id: orderId },
+ });
+}
+
+async function createOrder(createForm: CreateOrderItem): Promise {
+ return prisma.orderItem.create({
+ data: createForm,
+ });
+}
+
+async function updateOrder(orderId: string, updateForm: UpdateOrderItem): Promise {
+ return prisma.orderItem.update({
+ where: { id: orderId },
+ data: updateForm,
+ });
+}
+
+async function deleteOrder(orderId: string): Promise {
+ return prisma.orderItem.delete({
+ where: { id: orderId },
+ });
+}
+
+async function getOrdersByStatus(status: string): Promise {
+ return prisma.orderItem.findMany({
+ where: { status },
+ });
+}
+
+export {
+ getOrder,
+ listOrders,
+ createOrder,
+ updateOrder,
+ deleteOrder,
+ getOrdersByStatus,
+ type CreateOrderItem as CreateOrder,
+ type UpdateOrderItem as UpdateOrder,
+};
diff --git a/03_source/cms_backend/src/app/services/product.service.ts b/03_source/cms_backend/src/app/services/product.service.ts
new file mode 100644
index 0000000..c8b5967
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/product.service.ts
@@ -0,0 +1,114 @@
+// src/app/services/product.service.ts
+//
+// PURPOSE:
+// - Service for handling ProductItem Record
+//
+
+import type { ProductItem } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+type CreateProduct = {
+ sku: string;
+ name: string;
+ // price: number;
+ // code?: string;
+ // taxes?: number;
+ // tags?: string[];
+ // sizes?: string[];
+ // gender?: string[];
+ // colors?: string[];
+ // category?: string;
+ // quantity?: number;
+ // available?: number;
+ // coverUrl?: string;
+ // images?: string[];
+ // description?: string;
+ // subDescription?: string;
+ // publish?: string;
+ // totalSold?: number;
+ // totalRatings?: number;
+ // totalReviews?: number;
+ // inventoryType?: string;
+ // ratings?: number[];
+ // reviews?: string[];
+ // result?: string;
+ // thanks?: string;
+};
+
+type UpdateProduct = {
+ sku?: string;
+ name?: string;
+ // price?: number;
+ // code?: string;
+ // taxes?: number;
+ // tags?: string[];
+ // sizes?: string[];
+ // gender?: string[];
+ // colors?: string[];
+ // category?: string;
+ // quantity?: number;
+ // available?: number;
+ // coverUrl?: string;
+ // images?: string[];
+ // description?: string;
+ // subDescription?: string;
+ // publish?: string;
+ // totalSold?: number;
+ // totalRatings?: number;
+ // totalReviews?: number;
+ // inventoryType?: string;
+ // ratings?: number[];
+ // reviews?: string[];
+ // result?: string;
+ // thanks?: string;
+};
+
+async function listProducts(): Promise {
+ return prisma.productItem.findMany();
+}
+
+async function getProduct(productId: string) {
+ return prisma.productItem.findUnique({ where: { id: productId } });
+}
+
+async function createProduct(createForm: CreateProduct) {
+ // return prisma.productItem.create({
+ // data: {
+ // ...createForm,
+ // code: createForm.code || '',
+ // taxes: createForm.taxes || 0,
+ // tags: createForm.tags || [],
+ // sizes: createForm.sizes || [],
+ // gender: createForm.gender || [],
+ // colors: createForm.colors || [],
+ // category: createForm.category || '',
+ // quantity: createForm.quantity || 0,
+ // available: createForm.available || 0,
+ // coverUrl: createForm.coverUrl || '',
+ // images: createForm.images || [],
+ // description: createForm.description || '',
+ // subDescription: createForm.subDescription || '',
+ // publish: createForm.publish || 'published',
+ // totalSold: createForm.totalSold || 0,
+ // totalRatings: createForm.totalRatings || 0,
+ // totalReviews: createForm.totalReviews || 0,
+ // inventoryType: createForm.inventoryType || '',
+ // ratings: createForm.ratings || [],
+ // reviews: createForm.reviews || [],
+ // },
+ // });
+}
+
+async function updateProduct(productId: string, updateForm: UpdateProduct) {
+ return prisma.productItem.update({
+ where: { id: productId },
+ data: updateForm,
+ });
+}
+
+async function deleteProduct(productId: string) {
+ return prisma.productItem.delete({ where: { id: productId } });
+}
+
+export { getProduct, listProducts, createProduct, updateProduct, deleteProduct, type CreateProduct, type UpdateProduct };
diff --git a/03_source/cms_backend/src/app/services/userItem.service.ts b/03_source/cms_backend/src/app/services/userItem.service.ts
new file mode 100644
index 0000000..8b9cadc
--- /dev/null
+++ b/03_source/cms_backend/src/app/services/userItem.service.ts
@@ -0,0 +1,122 @@
+// src/app/services/user.service.ts
+//
+// PURPOSE:
+// - Handle User Record CRUD operations
+//
+// RULES:
+// - Follow Prisma best practices for database operations
+// - Validate input data before processing
+//
+
+import type { UserItem } from '@prisma/client';
+
+import prisma from '../lib/prisma';
+
+type CreateUser = {
+ email: string;
+ // name?: string;
+ // password: string;
+ // role?: Role;
+ // isEmailVerified?: boolean;
+ // admin?: boolean;
+};
+
+type UpdateUser = {
+ email?: string;
+ // name?: string;
+ // password?: string;
+ // role?: Role;
+ // isEmailVerified?: boolean;
+ isAdmin?: boolean;
+};
+
+async function listUsers(): Promise {
+ return prisma.userItem.findMany();
+}
+
+async function getUserItem(userId: string): Promise {
+ return prisma.userItem.findFirst({ where: { id: userId } });
+}
+
+async function updateUser(userId: string, updateForm: UpdateUser): Promise {
+ return prisma.userItem.update({
+ where: { id: userId },
+ data: updateForm,
+ });
+}
+
+// check if userId is a admin
+// check if userId is a admin
+async function isAdmin(userId: string): Promise {
+ const user = await getUserItem(userId);
+ return user?.isAdmin === true;
+}
+
+async function changeToAdmin(userIdToPromote: string, userIdOfApplicant: string) {
+ // check the applicant is admin or not
+ const userApplicant = await getUserItem(userIdOfApplicant);
+ let promoteResult = {};
+
+ if (userApplicant && userApplicant.isAdmin) {
+ // applicant is an admin
+ promoteResult = await updateUser(userIdToPromote, { isAdmin: true });
+ } else {
+ promoteResult = { status: 'failed', message: 'applicant is not a admin' };
+ }
+
+ return promoteResult;
+}
+
+async function changeToUser(userIdToPromote: string, userIdOfApplicant: string) {
+ // check the applicant is admin or not
+ const userApplicant = await getUserItem(userIdOfApplicant);
+ let promoteResult = {};
+
+ if (userApplicant && userApplicant.isAdmin) {
+ // applicant is an admin
+ promoteResult = await updateUser(userIdToPromote, { isAdmin: false });
+ } else {
+ promoteResult = { status: 'failed', message: 'applicant is not a admin' };
+ }
+
+ return promoteResult;
+}
+
+async function getUserByEmail(email: string): Promise {
+ // return prisma.userItem.findUnique({
+ // where: { email },
+ // include: {
+ // Token: true,
+ // },
+ // });
+}
+
+async function createNewUser(createForm: CreateUser): Promise {
+ // return prisma.userItem.create({
+ // data: {
+ // email: createForm.email,
+ // name: createForm.name,
+ // password: createForm.password,
+ // role: createForm.role || 'USER',
+ // isEmailVerified: createForm.isEmailVerified || false,
+ // },
+ // });
+}
+
+async function deleteUser(userId: number): Promise {
+ // return prisma.userItem.delete({ where: { id: userId } });
+}
+
+export {
+ // getUser,
+ isAdmin,
+ listUsers,
+ updateUser,
+ deleteUser,
+ changeToUser,
+ createNewUser,
+ changeToAdmin,
+ getUserByEmail,
+ type CreateUser,
+ type UpdateUser,
+};
diff --git a/03_source/cms_backend/src/utils/response.ts b/03_source/cms_backend/src/utils/response.ts
index 81bdaaa..78b3e09 100644
--- a/03_source/cms_backend/src/utils/response.ts
+++ b/03_source/cms_backend/src/utils/response.ts
@@ -7,7 +7,7 @@ export const STATUS = {
UNAUTHORIZED: 401,
};
-export function response(data: string | Record, status: number): Response {
+export function response(data: string | Record | Record[], status: number): Response {
const value = typeof data === 'string' ? data : JSON.stringify(data);
return new Response(value, {