Compare commits
93 Commits
develop/cm
...
develop/re
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ebc8a81641 | ||
![]() |
f487427b2c | ||
![]() |
cc4d035f97 | ||
![]() |
84b223ff60 | ||
![]() |
fca048074e | ||
![]() |
bc35e25616 | ||
![]() |
15f8d2e6aa | ||
![]() |
592a099f7b | ||
![]() |
4c1b30e5c6 | ||
![]() |
c765bb49a4 | ||
![]() |
9aeb58379d | ||
![]() |
6419567005 | ||
![]() |
e2076fe67b | ||
![]() |
766720e075 | ||
![]() |
19af60c410 | ||
![]() |
ed95621b2f | ||
![]() |
2258fd8fb9 | ||
![]() |
0f674badd9 | ||
![]() |
bc731ea2b8 | ||
![]() |
c11dba6297 | ||
![]() |
b9fbe4e476 | ||
![]() |
0d844eed3f | ||
![]() |
2862cddb6b | ||
![]() |
55509b483c | ||
![]() |
3f2ac2a285 | ||
![]() |
1216de4ff9 | ||
![]() |
7966d8abf5 | ||
![]() |
e975aebfcd | ||
![]() |
7f6970b183 | ||
![]() |
e83854ed2a | ||
![]() |
6961f058df | ||
![]() |
b515337acc | ||
![]() |
c732d89c34 | ||
![]() |
2b71d06c8d | ||
![]() |
3ec9d87f8b | ||
![]() |
546fb72732 | ||
![]() |
4303704753 | ||
![]() |
4bb213ef0c | ||
![]() |
657c652657 | ||
![]() |
5be77aae23 | ||
![]() |
ffbe63e421 | ||
![]() |
ba1e718039 | ||
![]() |
8b32d153db | ||
![]() |
d3e554b218 | ||
![]() |
5b10977a64 | ||
![]() |
a40b0fa4b1 | ||
![]() |
a4692a7d1f | ||
![]() |
c3f680aa22 | ||
![]() |
5b680f2219 | ||
![]() |
d3d95469ea | ||
![]() |
04eaf91d60 | ||
![]() |
be571ba4db | ||
![]() |
a6d549b2e8 | ||
![]() |
beb1e0ae68 | ||
![]() |
b2adcff61b | ||
![]() |
8200e0d902 | ||
![]() |
731cb7500e | ||
![]() |
d6c87e33f0 | ||
![]() |
e4b6c5e92d | ||
![]() |
03782cde24 | ||
![]() |
7cc6d939f5 | ||
![]() |
3bcb40c5ef | ||
![]() |
43a285dd2c | ||
![]() |
09a8dc539e | ||
![]() |
8f75226763 | ||
![]() |
7cfbcc1573 | ||
![]() |
5234ac06cd | ||
![]() |
aec59fb328 | ||
![]() |
7f9b4c2224 | ||
![]() |
197b006df3 | ||
![]() |
3692b2204b | ||
![]() |
3217a8d594 | ||
![]() |
d453144500 | ||
![]() |
e8d12f34e8 | ||
![]() |
56d43062c9 | ||
![]() |
c2a02cff77 | ||
![]() |
d3ef280b20 | ||
![]() |
b2e9616178 | ||
![]() |
d909805283 | ||
![]() |
8c46a93e61 | ||
![]() |
82507b4b31 | ||
![]() |
85651ff204 | ||
![]() |
9caca30e1e | ||
![]() |
d76d43d17f | ||
![]() |
dff07ddcb0 | ||
![]() |
b78709db9b | ||
![]() |
df9992454b | ||
![]() |
22fb620eef | ||
![]() |
c0fad42f0a | ||
![]() |
c2c6181332 | ||
![]() |
99239c32a5 | ||
![]() |
ef0c0ab389 | ||
![]() |
7610d80005 |
@@ -77,12 +77,12 @@ const Chat = () => {
|
||||
const [toastMessage, setToastMessage] = useState("");
|
||||
|
||||
// Refs
|
||||
const contentRef = useRef();
|
||||
const contentRef = useRef(null);
|
||||
const swiperRefs = useRef([]);
|
||||
const textareaRef = useRef();
|
||||
const sideRef = useRef();
|
||||
const sendRef = useRef();
|
||||
const replyToAnimationRef = useRef();
|
||||
const textareaRef = useRef(null);
|
||||
const sideRef = useRef(null);
|
||||
const sendRef = useRef(null);
|
||||
const replyToAnimationRef = useRef(null);
|
||||
|
||||
const actionSheetButtons = [
|
||||
{
|
||||
@@ -104,7 +104,7 @@ const Chat = () => {
|
||||
icon: alertOutline,
|
||||
handler: () =>
|
||||
toaster(
|
||||
"I haven't implemented unsend :) Simple store update though",
|
||||
"I haven't implemented unsend :) Simple store update though"
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -165,11 +165,11 @@ const Chat = () => {
|
||||
const chatMessageID = elementID.includes("chatText")
|
||||
? parseInt(elementID.replace("chatText_", ""))
|
||||
: elementID.includes("chatTime")
|
||||
? parseInt(elementID.replace("chatTime_", ""))
|
||||
: parseInt(elementID.replace("chatBubble_", ""));
|
||||
? parseInt(elementID.replace("chatTime_", ""))
|
||||
: parseInt(elementID.replace("chatBubble_", ""));
|
||||
|
||||
const chatMessage = chat.filter(
|
||||
(message) => parseInt(message.id) === parseInt(chatMessageID),
|
||||
(message) => parseInt(message.id) === parseInt(chatMessageID)
|
||||
)[0];
|
||||
|
||||
setActionMessage(chatMessage);
|
||||
@@ -278,7 +278,7 @@ const Chat = () => {
|
||||
replyToMessage,
|
||||
replyToMessage ? replyToMessage.id : false,
|
||||
image,
|
||||
imagePath,
|
||||
imagePath
|
||||
);
|
||||
setMessage("");
|
||||
|
||||
@@ -329,7 +329,7 @@ const Chat = () => {
|
||||
fill="clear"
|
||||
onClick={() =>
|
||||
toaster(
|
||||
"As this is a UI only, video calling wouldn't work here.",
|
||||
"As this is a UI only, video calling wouldn't work here."
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -352,7 +352,7 @@ const Chat = () => {
|
||||
{chat.map((message, index) => {
|
||||
const repliedMessage = chat.filter(
|
||||
(subMessage) =>
|
||||
parseInt(subMessage.id) === parseInt(message.replyID),
|
||||
parseInt(subMessage.id) === parseInt(message.replyID)
|
||||
)[0];
|
||||
|
||||
return (
|
||||
@@ -360,7 +360,9 @@ const Chat = () => {
|
||||
ref={(ref) => (swiperRefs.current[index] = ref)}
|
||||
id={`chatBubble_${message.id}`}
|
||||
key={index}
|
||||
className={`chat-bubble ${message.sent ? "bubble-sent" : "bubble-received"}`}
|
||||
className={`chat-bubble ${
|
||||
message.sent ? "bubble-sent" : "bubble-received"
|
||||
}`}
|
||||
{...longPressEvent}
|
||||
>
|
||||
<div id={`chatText_${message.id}`}>
|
||||
|
@@ -22,7 +22,7 @@ import { useRef } from "react";
|
||||
import ContactModal from "../components/ContactModal";
|
||||
|
||||
const Chats = () => {
|
||||
const pageRef = useRef();
|
||||
const pageRef = useRef(null);
|
||||
const contacts = ContactStore.useState(getContacts);
|
||||
const latestChats = ChatStore.useState(getChats);
|
||||
|
||||
@@ -43,7 +43,7 @@ const Chats = () => {
|
||||
contacts
|
||||
.filter((c) => c.id === chat.contact_id)[0]
|
||||
.name.toLowerCase()
|
||||
.includes(searchTermLower),
|
||||
.includes(searchTermLower)
|
||||
);
|
||||
setResults(newResults);
|
||||
} else {
|
||||
|
1
01_Requirements/REQ0119/index.md
Normal file
1
01_Requirements/REQ0119/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# Demo2FaExample
|
1
01_Requirements/REQ0120/index.md
Normal file
1
01_Requirements/REQ0120/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoAccordionTutorial
|
1
01_Requirements/REQ0121/index.md
Normal file
1
01_Requirements/REQ0121/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoBankingUi
|
1
01_Requirements/REQ0122/index.md
Normal file
1
01_Requirements/REQ0122/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoBlogPostUi
|
1
01_Requirements/REQ0123/index.md
Normal file
1
01_Requirements/REQ0123/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoCapacitorGoogleMapsTutorial
|
1
01_Requirements/REQ0124/index.md
Normal file
1
01_Requirements/REQ0124/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoClubHouse
|
1
01_Requirements/REQ0125/index.md
Normal file
1
01_Requirements/REQ0125/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoColorTutorial
|
1
01_Requirements/REQ0126/index.md
Normal file
1
01_Requirements/REQ0126/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoDictionaryApp
|
1
01_Requirements/REQ0127/index.md
Normal file
1
01_Requirements/REQ0127/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoEcommerceExample
|
1
01_Requirements/REQ0128/index.md
Normal file
1
01_Requirements/REQ0128/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoFacebookClone
|
1
01_Requirements/REQ0129/index.md
Normal file
1
01_Requirements/REQ0129/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoFastFoodApp
|
1
01_Requirements/REQ0130/index.md
Normal file
1
01_Requirements/REQ0130/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoFloatingTabs
|
1
01_Requirements/REQ0131/index.md
Normal file
1
01_Requirements/REQ0131/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoInstagramClone
|
1
01_Requirements/REQ0132/index.md
Normal file
1
01_Requirements/REQ0132/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoKanbanBoard
|
1
01_Requirements/REQ0133/index.md
Normal file
1
01_Requirements/REQ0133/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoList
|
1
01_Requirements/REQ0134/index.md
Normal file
1
01_Requirements/REQ0134/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoOrderingApp
|
1
01_Requirements/REQ0135/index.md
Normal file
1
01_Requirements/REQ0135/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoPinterestFloatingTabBar
|
1
01_Requirements/REQ0136/index.md
Normal file
1
01_Requirements/REQ0136/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoProfileExample
|
1
01_Requirements/REQ0137/index.md
Normal file
1
01_Requirements/REQ0137/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoPullstateTutorial
|
1
01_Requirements/REQ0138/index.md
Normal file
1
01_Requirements/REQ0138/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoQrScanner
|
1
01_Requirements/REQ0139/index.md
Normal file
1
01_Requirements/REQ0139/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoQuizApp
|
1
01_Requirements/REQ0140/index.md
Normal file
1
01_Requirements/REQ0140/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoQuoteApp
|
1
01_Requirements/REQ0141/index.md
Normal file
1
01_Requirements/REQ0141/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactAddToCart
|
1
01_Requirements/REQ0142/index.md
Normal file
1
01_Requirements/REQ0142/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactCalculator
|
1
01_Requirements/REQ0143/index.md
Normal file
1
01_Requirements/REQ0143/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactDrawingCanvas
|
1
01_Requirements/REQ0144/index.md
Normal file
1
01_Requirements/REQ0144/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactHookFormExample
|
1
01_Requirements/REQ0145/index.md
Normal file
1
01_Requirements/REQ0145/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactItemList
|
1
01_Requirements/REQ0146/index.md
Normal file
1
01_Requirements/REQ0146/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactLifecycles
|
1
01_Requirements/REQ0147/index.md
Normal file
1
01_Requirements/REQ0147/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactLogin
|
1
01_Requirements/REQ0148/index.md
Normal file
1
01_Requirements/REQ0148/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactMarvelApp
|
1
01_Requirements/REQ0149/index.md
Normal file
1
01_Requirements/REQ0149/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactMovieAppWithAlgolia
|
1
01_Requirements/REQ0150/index.md
Normal file
1
01_Requirements/REQ0150/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactNotes
|
1
01_Requirements/REQ0151/index.md
Normal file
1
01_Requirements/REQ0151/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactOnboardingUi
|
1
01_Requirements/REQ0152/index.md
Normal file
1
01_Requirements/REQ0152/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactOverlayHooks
|
1
01_Requirements/REQ0153/index.md
Normal file
1
01_Requirements/REQ0153/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactPollApp
|
1
01_Requirements/REQ0154/index.md
Normal file
1
01_Requirements/REQ0154/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactProfileDashboardUi
|
1
01_Requirements/REQ0155/index.md
Normal file
1
01_Requirements/REQ0155/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactQuotes
|
1
01_Requirements/REQ0156/index.md
Normal file
1
01_Requirements/REQ0156/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactShop
|
1
01_Requirements/REQ0157/index.md
Normal file
1
01_Requirements/REQ0157/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactShopUi
|
1
01_Requirements/REQ0158/index.md
Normal file
1
01_Requirements/REQ0158/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactSwitchTabs
|
1
01_Requirements/REQ0159/index.md
Normal file
1
01_Requirements/REQ0159/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactTabsMenusCustom
|
1
01_Requirements/REQ0160/index.md
Normal file
1
01_Requirements/REQ0160/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactThemeSwitcher
|
1
01_Requirements/REQ0161/index.md
Normal file
1
01_Requirements/REQ0161/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoReactTravelApp
|
1
01_Requirements/REQ0162/index.md
Normal file
1
01_Requirements/REQ0162/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoRecipeApp
|
1
01_Requirements/REQ0163/index.md
Normal file
1
01_Requirements/REQ0163/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoRestaurantFinder
|
1
01_Requirements/REQ0164/index.md
Normal file
1
01_Requirements/REQ0164/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoScoreBoard
|
1
01_Requirements/REQ0165/index.md
Normal file
1
01_Requirements/REQ0165/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoShopAppUi
|
1
01_Requirements/REQ0166/index.md
Normal file
1
01_Requirements/REQ0166/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoSkeletonText
|
1
01_Requirements/REQ0167/index.md
Normal file
1
01_Requirements/REQ0167/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoSlidingProfile
|
1
01_Requirements/REQ0168/index.md
Normal file
1
01_Requirements/REQ0168/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoStickyBottomSheetExample
|
1
01_Requirements/REQ0169/index.md
Normal file
1
01_Requirements/REQ0169/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoStorageExample
|
1
01_Requirements/REQ0170/index.md
Normal file
1
01_Requirements/REQ0170/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# DemoWeatherAppUi
|
16
01_Requirements/REQ0180/index.md
Normal file
16
01_Requirements/REQ0180/index.md
Normal file
@@ -0,0 +1,16 @@
|
||||
---
|
||||
tags: docker, ports
|
||||
---
|
||||
|
||||
# REQ0180 service port schedule
|
||||
|
||||
## Port Usage
|
||||
|
||||
| Service | Host Port | Container Port | Environment | Purpose |
|
||||
| ----------- | --------- | -------------- | ----------- | -------------------------------- |
|
||||
| Frontend | 10001 | 8080 | All | Web application access |
|
||||
| Mobile | 10004 | 3000 | All | Mobile application access |
|
||||
| CMS Backend | 10002 | 7272 | All | Main API access |
|
||||
| CMS Backend | 10003 | 5555 | All | Additional service access |
|
||||
| Postgres | - | 5432 | Production | Database access (container only) |
|
||||
| Postgres | 5432 | 5432 | Development | Database access |
|
@@ -28,7 +28,8 @@
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push --force-reset",
|
||||
"db:push:w": "npx nodemon --delay 1 --watch prisma --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed\"",
|
||||
"db:studio": "prisma studio"
|
||||
"db:studio": "prisma studio",
|
||||
"db:studio:w": "npx nodemon --delay 1 --watch prisma --ext \"prisma\" --exec \"yarn db:studio\""
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
|
@@ -71,6 +71,15 @@ model VerificationToken {
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
|
||||
model Student {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
email String @unique
|
||||
metadata Json @default("{}")
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// frontend/src/_mock/_user.ts
|
||||
|
||||
@@ -525,6 +534,7 @@ model UserCard {
|
||||
totalFollowing Float
|
||||
}
|
||||
|
||||
// `UserItem` obsoleted, use `UserMeta` instead
|
||||
model UserItem {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
@@ -550,6 +560,31 @@ model UserItem {
|
||||
isAdmin Boolean @default(false)
|
||||
}
|
||||
|
||||
model UserMeta {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
//
|
||||
name String
|
||||
city String
|
||||
role String
|
||||
email String
|
||||
state String
|
||||
status String
|
||||
address String
|
||||
country String
|
||||
zipCode String
|
||||
company String
|
||||
avatarUrl String
|
||||
phoneNumber String
|
||||
isVerified Boolean
|
||||
//
|
||||
username String
|
||||
password String
|
||||
//
|
||||
isAdmin Boolean @default(false)
|
||||
}
|
||||
|
||||
model UserAccountBillingHistory {
|
||||
id Int @id @default(autoincrement())
|
||||
createdAt DateTime @default(now())
|
||||
|
@@ -30,6 +30,7 @@ import { EventItemSeed } from './seeds/eventItem';
|
||||
import { EventReviewSeed } from './seeds/eventReview';
|
||||
import { appLogSeed } from './seeds/AppLog';
|
||||
import { accessLogSeed } from './seeds/AccessLog';
|
||||
import { userMetaSeed } from './seeds/userMeta';
|
||||
|
||||
//
|
||||
// import { Blog } from './seeds/blog';
|
||||
@@ -46,7 +47,10 @@ import { accessLogSeed } from './seeds/AccessLog';
|
||||
await ProductReview;
|
||||
await FileStore;
|
||||
await ProductItem;
|
||||
|
||||
await userItemSeed;
|
||||
await userMetaSeed;
|
||||
|
||||
await orderItemSeed;
|
||||
await invoiceItemSeed;
|
||||
//
|
||||
|
130
03_source/cms_backend/prisma/seeds/userMeta.ts
Normal file
130
03_source/cms_backend/prisma/seeds/userMeta.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { generateHash } from 'src/utils/hash';
|
||||
import { Config, names, uniqueNamesGenerator } from 'unique-names-generator';
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { faker as enFaker } from '@faker-js/faker/locale/en_US';
|
||||
import { faker as zhFaker } from '@faker-js/faker/locale/zh_CN';
|
||||
import { faker as jaFaker } from '@faker-js/faker/locale/ja';
|
||||
import { faker as koFaker } from '@faker-js/faker/locale/ko';
|
||||
import { faker as twFaker } from '@faker-js/faker/locale/zh_TW';
|
||||
|
||||
const SEED_EMAIL_DOMAIN = 'seed.com';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function userMeta() {
|
||||
const config: Config = { dictionaries: [names] };
|
||||
const firstName = uniqueNamesGenerator(config);
|
||||
const lastName = uniqueNamesGenerator(config);
|
||||
const username = `${firstName.toLowerCase()}-${lastName.toLowerCase()}`;
|
||||
|
||||
const alice = await prisma.userMeta.upsert({
|
||||
where: { id: 'admin_uuid' },
|
||||
update: {},
|
||||
create: {
|
||||
name: `admin test`,
|
||||
city: '',
|
||||
role: '',
|
||||
email: `admin@123.com`,
|
||||
state: '',
|
||||
status: '',
|
||||
address: '',
|
||||
country: '',
|
||||
zipCode: '',
|
||||
company: '',
|
||||
avatarUrl: '',
|
||||
phoneNumber: '',
|
||||
isVerified: true,
|
||||
//
|
||||
username: 'admin@123.com',
|
||||
password: await generateHash('Aa1234567'),
|
||||
//
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 1; i < 3; i++) {
|
||||
const CJK_LOCALES = {
|
||||
en: enFaker,
|
||||
zh: zhFaker,
|
||||
ja: jaFaker,
|
||||
ko: koFaker,
|
||||
tw: twFaker,
|
||||
};
|
||||
function getRandomCJKFaker() {
|
||||
const locales = Object.keys(CJK_LOCALES);
|
||||
const randomKey = locales[Math.floor(Math.random() * locales.length)] as keyof typeof CJK_LOCALES;
|
||||
return CJK_LOCALES[randomKey];
|
||||
}
|
||||
const randomFaker = getRandomCJKFaker();
|
||||
|
||||
await prisma.userMeta.upsert({
|
||||
where: { id: i.toString() },
|
||||
update: {},
|
||||
create: {
|
||||
name: randomFaker.person.fullName(),
|
||||
city: randomFaker.location.city(),
|
||||
role: ROLE[Math.floor(Math.random() * ROLE.length)],
|
||||
email: randomFaker.internet.email(),
|
||||
state: randomFaker.location.state(),
|
||||
status: STATUS[Math.floor(Math.random() * STATUS.length)],
|
||||
address: randomFaker.location.streetAddress(),
|
||||
country: randomFaker.location.country(),
|
||||
zipCode: randomFaker.location.zipCode(),
|
||||
company: randomFaker.company.name(),
|
||||
avatarUrl: randomFaker.image.avatar(),
|
||||
phoneNumber: randomFaker.phone.number(),
|
||||
isVerified: true,
|
||||
//
|
||||
username: randomFaker.internet.username(),
|
||||
password: await generateHash('Abc1234!'),
|
||||
//
|
||||
isAdmin: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log('seed userMeta done');
|
||||
}
|
||||
|
||||
const userMetaSeed = userMeta()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error(e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
export { userMetaSeed };
|
||||
|
||||
const ROLE = [
|
||||
`CEO`,
|
||||
`CTO`,
|
||||
`Project Coordinator`,
|
||||
`Team Leader`,
|
||||
`Software Developer`,
|
||||
`Marketing Strategist`,
|
||||
`Data Analyst`,
|
||||
`Product Owner`,
|
||||
`Graphic Designer`,
|
||||
`Operations Manager`,
|
||||
`Customer Support Specialist`,
|
||||
`Sales Manager`,
|
||||
`HR Recruiter`,
|
||||
`Business Consultant`,
|
||||
`Financial Planner`,
|
||||
`Network Engineer`,
|
||||
`Content Creator`,
|
||||
`Quality Assurance Tester`,
|
||||
`Public Relations Officer`,
|
||||
`IT Administrator`,
|
||||
`Compliance Officer`,
|
||||
`Event Planner`,
|
||||
`Legal Counsel`,
|
||||
`Training Coordinator`,
|
||||
];
|
||||
|
||||
const STATUS = ['active', 'pending', 'banned'];
|
@@ -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);
|
||||
|
@@ -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);
|
||||
}
|
||||
|
25
03_source/cms_backend/src/app/api/auth/me/test.http
Normal file
25
03_source/cms_backend/src/app/api/auth/me/test.http
Normal file
@@ -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"
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
|
||||
export function flattenNextjsRequest(req: NextRequest) {
|
||||
return Object.fromEntries(req.headers.entries());
|
||||
}
|
@@ -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();
|
||||
|
25
03_source/cms_backend/src/app/api/event/_GUIDELINES.md
Normal file
25
03_source/cms_backend/src/app/api/event/_GUIDELINES.md
Normal file
@@ -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
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -1,3 +1,5 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/event/details?eventId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
|
||||
|
||||
###
|
||||
GET http://localhost:7272/api/event/details
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
3
03_source/cms_backend/src/app/api/product/_GUIDELINES.md
Normal file
3
03_source/cms_backend/src/app/api/product/_GUIDELINES.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# GUIDELINES
|
||||
|
||||
T.B.A.
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/product/details?productId=e99f09a7-dd88-49d5-b1c8-1daf80c2d7b01
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
3
03_source/cms_backend/src/app/api/product/list/test.http
Normal file
3
03_source/cms_backend/src/app/api/product/list/test.http
Normal file
@@ -0,0 +1,3 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/product/list
|
23
03_source/cms_backend/src/app/api/student/_GUIDELINES.md
Normal file
23
03_source/cms_backend/src/app/api/student/_GUIDELINES.md
Normal file
@@ -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
|
39
03_source/cms_backend/src/app/api/student/detail/route.ts
Normal file
39
03_source/cms_backend/src/app/api/student/detail/route.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
###
|
||||
|
||||
GET http://localhost:7272/api/helloworld/details?helloworldId=1165ce3a-29b8-4e1a-9148-1ae08d7e8e01
|
||||
|
34
03_source/cms_backend/src/app/api/student/route.ts
Normal file
34
03_source/cms_backend/src/app/api/student/route.ts
Normal file
@@ -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 });
|
||||
}
|
||||
}
|
25
03_source/cms_backend/src/app/api/student/test.http
Normal file
25
03_source/cms_backend/src/app/api/student/test.http
Normal file
@@ -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
|
@@ -16,6 +16,7 @@ import prisma from '../lib/prisma';
|
||||
type CreateAppLog = {
|
||||
level: number;
|
||||
message: string;
|
||||
metadata: Record<string, any>;
|
||||
};
|
||||
|
||||
// type UpdateAppLog = {
|
||||
@@ -31,6 +32,10 @@ async function getAppLog(appLogId: string) {
|
||||
return prisma.appLog.findFirst({ where: { id: appLogId } });
|
||||
}
|
||||
|
||||
async function createAppLog(level: number, message: string, metadata: Record<string, any>) {
|
||||
return prisma.appLog.create({ data: { level, message, metadata } });
|
||||
}
|
||||
|
||||
async function createNewAppLog(createForm: CreateAppLog) {
|
||||
return prisma.appLog.create({ data: createForm });
|
||||
}
|
||||
@@ -48,5 +53,6 @@ export {
|
||||
listAppLogs,
|
||||
// updateAppLog,
|
||||
deleteAppLog,
|
||||
createAppLog,
|
||||
createNewAppLog,
|
||||
};
|
||||
|
@@ -7,10 +7,6 @@
|
||||
// - Follows same pattern as helloworld.service.ts
|
||||
//
|
||||
|
||||
import type { Event } from '@prisma/client';
|
||||
|
||||
import prisma from '../lib/prisma';
|
||||
|
||||
type CreateEvent = {
|
||||
eventDate: DateTime;
|
||||
title: string;
|
||||
@@ -39,27 +35,33 @@ type UpdateEvent = {
|
||||
memberId?: number;
|
||||
};
|
||||
|
||||
async function listEvents(): Promise<Event[]> {
|
||||
return prisma.event.findMany();
|
||||
}
|
||||
// async function listEvents(): Promise<Event[]> {
|
||||
// return prisma.event.findMany();
|
||||
// }
|
||||
|
||||
async function getEvent(eventId: number) {
|
||||
return prisma.event.findFirst({ where: { id: eventId } });
|
||||
}
|
||||
// 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 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 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 } });
|
||||
}
|
||||
// async function deleteEvent(eventId: number) {
|
||||
// return prisma.event.delete({ where: { id: eventId } });
|
||||
// }
|
||||
|
||||
export { getEvent, listEvents, updateEvent, deleteEvent, createNewEvent };
|
||||
export {
|
||||
listEvents,
|
||||
// getEvent,
|
||||
// updateEvent,
|
||||
// deleteEvent,
|
||||
// createNewEvent,
|
||||
};
|
||||
|
71
03_source/cms_backend/src/app/services/eventItem.service.ts
Normal file
71
03_source/cms_backend/src/app/services/eventItem.service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// src/app/services/event.service.ts
|
||||
//
|
||||
// PURPOSE:
|
||||
// - Service for handling Event records
|
||||
//
|
||||
// RULES:
|
||||
// - Follows same pattern as helloworld.service.ts
|
||||
//
|
||||
|
||||
import type { EventItem } 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<EventItem[]> {
|
||||
return prisma.eventItem.findMany();
|
||||
}
|
||||
|
||||
async function getEvent(eventId: string): Promise<EventItem | null> {
|
||||
return prisma.eventItem.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,
|
||||
};
|
@@ -68,7 +68,7 @@ async function listProducts(): Promise<ProductItem[]> {
|
||||
return prisma.productItem.findMany();
|
||||
}
|
||||
|
||||
async function getProduct(productId: string) {
|
||||
async function getProduct(productId: string): Promise<ProductItem | null> {
|
||||
return prisma.productItem.findUnique({ where: { id: productId } });
|
||||
}
|
||||
|
||||
|
127
03_source/cms_backend/src/app/services/user.service.ts
Normal file
127
03_source/cms_backend/src/app/services/user.service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
// 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 { User } 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<UserItem[]> {
|
||||
// return prisma.userItem.findMany();
|
||||
// }
|
||||
|
||||
// async function getUserItem(userId: string): Promise<UserItem | null> {
|
||||
// return prisma.userItem.findFirst({ where: { id: userId } });
|
||||
// }
|
||||
|
||||
// async function updateUser(userId: string, updateForm: UpdateUser): Promise<User> {
|
||||
// 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<boolean> {
|
||||
// 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 getUserById(id: string): Promise<User | null> {
|
||||
return prisma.user.findFirst({ where: { id } });
|
||||
}
|
||||
|
||||
// async function getUserByEmail(email: string): Promise<void> {
|
||||
// // return prisma.userItem.findUnique({
|
||||
// // where: { email },
|
||||
// // include: {
|
||||
// // Token: true,
|
||||
// // },
|
||||
// // });
|
||||
// }
|
||||
|
||||
// async function createNewUser(createForm: CreateUser): Promise<void> {
|
||||
// // 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<void> {
|
||||
// // return prisma.userItem.delete({ where: { id: userId } });
|
||||
// }
|
||||
|
||||
export {
|
||||
// getUser,
|
||||
getUserById,
|
||||
// isAdmin,
|
||||
// listUsers,
|
||||
// updateUser,
|
||||
// deleteUser,
|
||||
// changeToUser,
|
||||
// createNewUser,
|
||||
// changeToAdmin,
|
||||
// getUserByEmail,
|
||||
// type CreateUser,
|
||||
// type UpdateUser,
|
||||
};
|
@@ -82,6 +82,10 @@ async function changeToUser(userIdToPromote: string, userIdOfApplicant: string)
|
||||
return promoteResult;
|
||||
}
|
||||
|
||||
async function getUserById1(id: string): Promise<UserItem | null> {
|
||||
return prisma.userItem.findFirst({ where: { id } });
|
||||
}
|
||||
|
||||
async function getUserByEmail(email: string): Promise<void> {
|
||||
// return prisma.userItem.findUnique({
|
||||
// where: { email },
|
||||
@@ -113,6 +117,7 @@ export {
|
||||
listUsers,
|
||||
updateUser,
|
||||
deleteUser,
|
||||
getUserById,
|
||||
changeToUser,
|
||||
createNewUser,
|
||||
changeToAdmin,
|
||||
|
7
03_source/cms_backend/src/constants.ts
Normal file
7
03_source/cms_backend/src/constants.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
const L_ERROR = 0;
|
||||
const L_WARN = 1;
|
||||
const L_INFO = 2;
|
||||
const L_DEBUG = 3;
|
||||
const L_TRACE = 4;
|
||||
|
||||
export { L_WARN, L_INFO, L_ERROR, L_DEBUG, L_TRACE };
|
@@ -12,11 +12,7 @@ type Token = {
|
||||
* SignJWT
|
||||
* https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md
|
||||
*/
|
||||
export async function sign(
|
||||
payload: Token,
|
||||
secret: string,
|
||||
options: { expiresIn: string | number }
|
||||
): Promise<string> {
|
||||
export async function sign(payload: Token, secret: string, options: { expiresIn: string | number }): Promise<string> {
|
||||
const iat = Math.floor(Date.now() / 1000);
|
||||
|
||||
return new SignJWT({ ...payload })
|
||||
|
16
03_source/docker/81_dc_down.sh
Executable file
16
03_source/docker/81_dc_down.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# -f docker-compose.db.yml
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml -f docker-compose.dev.yml"
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES build
|
||||
docker compose $DOCKER_COMPOSE_FILES kill
|
||||
docker compose $DOCKER_COMPOSE_FILES down
|
||||
|
||||
# cd ../api_server
|
||||
# yarn docker:dev
|
||||
# cd ..
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES logs -f
|
16
03_source/docker/91_prod_down.sh
Executable file
16
03_source/docker/91_prod_down.sh
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# -f docker-compose.db.yml
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml"
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES build
|
||||
docker compose $DOCKER_COMPOSE_FILES kill
|
||||
docker compose $DOCKER_COMPOSE_FILES down
|
||||
|
||||
# cd ../api_server
|
||||
# yarn docker:dev
|
||||
# cd ..
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES logs -f
|
17
03_source/docker/99_prod_up.sh
Executable file
17
03_source/docker/99_prod_up.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
|
||||
# -f docker-compose.db.yml
|
||||
DOCKER_COMPOSE_FILES=" -f docker-compose.yml"
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES build
|
||||
docker compose $DOCKER_COMPOSE_FILES up -d
|
||||
|
||||
docker compose $DOCKER_COMPOSE_FILES logs -f
|
||||
|
||||
# cd ../api_server
|
||||
# yarn docker:dev
|
||||
# cd ..
|
||||
|
||||
# docker compose $DOCKER_COMPOSE_FILES logs -f
|
@@ -1,9 +1,14 @@
|
||||
services:
|
||||
frontend:
|
||||
command: "sleep infinity"
|
||||
command: 'sleep infinity'
|
||||
|
||||
mobile:
|
||||
command: "sleep infinity"
|
||||
command: 'sleep infinity'
|
||||
|
||||
cms_backend:
|
||||
command: "sleep infinity"
|
||||
command: 'sleep infinity'
|
||||
|
||||
postgres:
|
||||
# container_name: postgres
|
||||
ports:
|
||||
- '5432:5432'
|
||||
|
@@ -8,22 +8,22 @@ services:
|
||||
build: ../frontend
|
||||
# user: 1000:1000
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 10001:8080
|
||||
volumes:
|
||||
- ../frontend:/app
|
||||
working_dir: "/app"
|
||||
command: "yarn dev"
|
||||
working_dir: '/app'
|
||||
command: './dev.sh'
|
||||
|
||||
mobile:
|
||||
image: 192.168.10.61:5000/hksingleparty_mobile
|
||||
build: ../mobile
|
||||
# user: 1000:1000
|
||||
ports:
|
||||
- 8081:3000
|
||||
- 10004:3000
|
||||
volumes:
|
||||
- ../mobile:/app
|
||||
working_dir: "/app"
|
||||
command: "npm run dev"
|
||||
working_dir: '/app'
|
||||
command: './dev.sh'
|
||||
|
||||
cms_backend:
|
||||
image: 192.168.10.61:5000/demo_minimal_kit_backend
|
||||
@@ -32,22 +32,19 @@ services:
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 7272:7272
|
||||
- 5555:5555
|
||||
- 10002:7272
|
||||
- 10003:5555
|
||||
volumes:
|
||||
- ../cms_backend:/app
|
||||
working_dir: "/app"
|
||||
command: "yarn dev"
|
||||
working_dir: '/app'
|
||||
command: './dev.sh'
|
||||
|
||||
postgres:
|
||||
container_name: postgres
|
||||
image: postgres:14.1-alpine
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
expose:
|
||||
- "5432"
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- '5432'
|
||||
volumes:
|
||||
- db:/var/lib/postgresql/data
|
||||
|
@@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
yarn --dev
|
||||
|
||||
while true; do
|
||||
yarn --dev
|
||||
|
||||
# yarn tsc:print
|
||||
yarn lint:print
|
||||
|
||||
yarn dev --force --clearScreen
|
||||
|
||||
|
1721
03_source/frontend/eslint-show-config.json
Normal file
1721
03_source/frontend/eslint-show-config.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -105,6 +105,7 @@
|
||||
"react-organizational-chart": "^2.2.1",
|
||||
"react-phone-number-input": "^3.4.12",
|
||||
"react-router": "^7.4.1",
|
||||
"react-use": "^17.6.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
// src/actions/invoice.ts
|
||||
import { useMemo } from 'react';
|
||||
import axiosInstance, { endpoints, fetcher } from 'src/lib/axios';
|
||||
import type { IInvoiceItem } from 'src/types/invoice';
|
||||
import type { IInvoiceItem, SaveInvoiceData } from 'src/types/invoice';
|
||||
import type { SWRConfiguration } from 'swr';
|
||||
import useSWR from 'swr';
|
||||
|
||||
@@ -97,7 +97,7 @@ export function useSearchInvoices(query: string) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SaveInvoiceData = IInvoiceItem;
|
||||
// type SaveInvoiceData = IInvoiceItem;
|
||||
|
||||
export async function saveInvoice(invoiceId: string, saveInvoiceData: SaveInvoiceData) {
|
||||
const url = endpoints.invoice.saveInvoice(invoiceId);
|
||||
|
@@ -99,6 +99,7 @@ export function useSearchProducts(query: string) {
|
||||
|
||||
type SaveProductData = {
|
||||
// id: string;
|
||||
|
||||
sku: string;
|
||||
name: string;
|
||||
code: string;
|
||||
|
@@ -17,6 +17,8 @@ export type SignUpParams = {
|
||||
lastName: string;
|
||||
};
|
||||
|
||||
const ERR_ACCESS_TOKEN_NOT_FOUND = `Access token not found in response`;
|
||||
|
||||
/** **************************************
|
||||
* Sign in
|
||||
*************************************** */
|
||||
@@ -29,7 +31,7 @@ export const signInWithPassword = async ({ email, password }: SignInParams): Pro
|
||||
const { accessToken } = res.data;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('Access token not found in response');
|
||||
throw new Error(ERR_ACCESS_TOKEN_NOT_FOUND);
|
||||
}
|
||||
|
||||
setSession(accessToken);
|
||||
|
@@ -69,6 +69,8 @@ export function tokenExpired(exp: number) {
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
const INVALID_ACCESS_TOKEN = 'Invalid access token!';
|
||||
|
||||
export async function setSession(accessToken: string | null) {
|
||||
try {
|
||||
if (accessToken) {
|
||||
@@ -81,7 +83,7 @@ export async function setSession(accessToken: string | null) {
|
||||
if (decodedToken && 'exp' in decodedToken) {
|
||||
tokenExpired(decodedToken.exp);
|
||||
} else {
|
||||
throw new Error('Invalid access token!');
|
||||
throw new Error(INVALID_ACCESS_TOKEN);
|
||||
}
|
||||
} else {
|
||||
sessionStorage.removeItem(JWT_STORAGE_KEY);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user