diff --git a/Backend/nodemon.json b/Backend/nodemon.json index 886cb942..881cd46b 100644 --- a/Backend/nodemon.json +++ b/Backend/nodemon.json @@ -4,10 +4,13 @@ ], "ext": "ts,json", "ignore": [ - "src/**/*.test.ts", - "node_modules" + "node_modules", + "dist", + "src/routes/routes.ts", + "src/public/swagger.json", + "src/**/*.test.ts" ], - "exec": "ts-node -r tsconfig-paths/register src/server.ts", + "exec": "npm run build && node dist/server.js", "env": { "NODE_ENV": "development" } diff --git a/Backend/package-lock.json b/Backend/package-lock.json index ee4d9e9b..756e8947 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -45,6 +45,7 @@ "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", + "tsx": "^4.21.0", "typescript": "^5.7.3" } }, @@ -609,6 +610,448 @@ "kuler": "^2.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -4172,6 +4615,48 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -5033,6 +5518,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7707,6 +8205,16 @@ "node": ">=4" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -8725,6 +9233,26 @@ "yarn": ">=1.9.4" } }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/Backend/package.json b/Backend/package.json index 36df8ca4..e5bf9724 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -5,7 +5,7 @@ "main": "dist/server.js", "scripts": { "dev": "nodemon", - "build": "tsoa spec-and-routes && tsc", + "build": "npm run tsoa:gen && tsc", "start": "node dist/server.js", "tsoa:gen": "tsoa spec-and-routes", "prisma:generate": "prisma generate", @@ -18,15 +18,6 @@ "lint": "eslint . --ext .ts", "format": "prettier --write \"src/**/*.ts\"" }, - "keywords": [ - "e-learning", - "typescript", - "tsoa", - "express", - "prisma" - ], - "author": "", - "license": "ISC", "dependencies": { "@prisma/client": "^5.22.0", "bcrypt": "^5.1.1", @@ -62,11 +53,7 @@ "prisma": "^5.22.0", "supertest": "^7.0.0", "ts-jest": "^29.2.5", - "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.7.3" - }, - "prisma": { - "seed": "node prisma/seed.js" } -} +} \ No newline at end of file diff --git a/Backend/prisma/seed.js b/Backend/prisma/seed.js index 0ca6a8b2..4cc5371c 100644 --- a/Backend/prisma/seed.js +++ b/Backend/prisma/seed.js @@ -37,22 +37,28 @@ async function main() { // Seed Roles console.log('ðŸ‘Ĩ Seeding roles...'); const roles = await Promise.all([ - prisma.role.create({ - data: { + prisma.role.upsert({ + where: { code: 'ADMIN' }, + update: {}, + create: { code: 'ADMIN', name: { th: 'āļœāļđāđ‰āļ”āļđāđāļĨāļĢāļ°āļšāļš', en: 'Administrator' }, description: { th: 'āļĄāļĩāļŠāļīāļ—āļ˜āļīāđŒāđ€āļ‚āđ‰āļēāļ–āļķāļ‡āļ—āļļāļāļŸāļąāļ‡āļāđŒāļŠāļąāļ™', en: 'Full system access' } } }), - prisma.role.create({ - data: { + prisma.role.upsert({ + where: { code: 'INSTRUCTOR' }, + update: {}, + create: { code: 'INSTRUCTOR', name: { th: 'āļœāļđāđ‰āļŠāļ­āļ™', en: 'Instructor' }, description: { th: 'āļŠāļēāļĄāļēāļĢāļ–āļŠāļĢāđ‰āļēāļ‡āđāļĨāļ°āļˆāļąāļ”āļāļēāļĢāļ„āļ­āļĢāđŒāļŠāđ€āļĢāļĩāļĒāļ™', en: 'Can create and manage courses' } } }), - prisma.role.create({ - data: { + prisma.role.upsert({ + where: { code: 'STUDENT' }, + update: {}, + create: { code: 'STUDENT', name: { th: 'āļ™āļąāļāđ€āļĢāļĩāļĒāļ™', en: 'Student' }, description: { th: 'āļŠāļēāļĄāļēāļĢāļ–āļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āđāļĨāļ°āđ€āļĢāļĩāļĒāļ™āļ„āļ­āļĢāđŒāļŠ', en: 'Can enroll and learn courses' } @@ -64,8 +70,10 @@ async function main() { console.log('ðŸ‘Ī Seeding users...'); const hashedPassword = await bcrypt.hash('admin123', 10); - const admin = await prisma.user.create({ - data: { + const admin = await prisma.user.upsert({ + where: { username: 'admin' }, + update: {}, + create: { username: 'admin', email: 'admin@elearning.local', password: hashedPassword, @@ -81,8 +89,10 @@ async function main() { } }); - const instructor = await prisma.user.create({ - data: { + const instructor = await prisma.user.upsert({ + where: { username: 'instructor' }, + update: {}, + create: { username: 'instructor', email: 'instructor@elearning.local', password: hashedPassword, @@ -98,8 +108,10 @@ async function main() { } }); - const student = await prisma.user.create({ - data: { + const student = await prisma.user.upsert({ + where: { username: 'student' }, + update: {}, + create: { username: 'student', email: 'student@elearning.local', password: hashedPassword, @@ -118,8 +130,10 @@ async function main() { // Seed Categories console.log('📚 Seeding categories...'); const categories = await Promise.all([ - prisma.category.create({ - data: { + prisma.category.upsert({ + where: { slug: 'programming' }, + update: {}, + create: { name: { th: 'āļāļēāļĢāđ€āļ‚āļĩāļĒāļ™āđ‚āļ›āļĢāđāļāļĢāļĄ', en: 'Programming' }, slug: 'programming', description: { th: 'āđ€āļĢāļĩāļĒāļ™āļĢāļđāđ‰āļāļēāļĢāđ€āļ‚āļĩāļĒāļ™āđ‚āļ›āļĢāđāļāļĢāļĄāđāļĨāļ°āļžāļąāļ’āļ™āļēāļ‹āļ­āļŸāļ•āđŒāđāļ§āļĢāđŒ', en: 'Learn programming and software development' }, @@ -128,8 +142,10 @@ async function main() { created_by: admin.id } }), - prisma.category.create({ - data: { + prisma.category.upsert({ + where: { slug: 'design' }, + update: {}, + create: { name: { th: 'āļāļēāļĢāļ­āļ­āļāđāļšāļš', en: 'Design' }, slug: 'design', description: { th: 'āđ€āļĢāļĩāļĒāļ™āļĢāļđāđ‰āļāļēāļĢāļ­āļ­āļāđāļšāļšāļāļĢāļēāļŸāļīāļāđāļĨāļ° UI/UX', en: 'Learn graphic design and UI/UX' }, @@ -138,8 +154,10 @@ async function main() { created_by: admin.id } }), - prisma.category.create({ - data: { + prisma.category.upsert({ + where: { slug: 'business' }, + update: {}, + create: { name: { th: 'āļ˜āļļāļĢāļāļīāļˆ', en: 'Business' }, slug: 'business', description: { th: 'āđ€āļĢāļĩāļĒāļ™āļĢāļđāđ‰āļāļēāļĢāļšāļĢāļīāļŦāļēāļĢāļ˜āļļāļĢāļāļīāļˆāđāļĨāļ°āļāļēāļĢāļ•āļĨāļēāļ”', en: 'Learn business management and marketing' }, @@ -152,7 +170,15 @@ async function main() { // Seed Sample Course console.log('🎓 Seeding sample course...'); - const course = await prisma.course.create({ + + // Check if course already exists + let course = await prisma.course.findUnique({ + where: { slug: 'javascript-fundamentals' }, + include: { chapters: { include: { lessons: true } } } + }); + + if (!course) { + course = await prisma.course.create({ data: { title: { th: 'āļžāļ·āđ‰āļ™āļāļēāļ™ JavaScript', en: 'JavaScript Fundamentals' }, slug: 'javascript-fundamentals', @@ -351,6 +377,9 @@ async function main() { created_by: instructor.id } }); + } else { + console.log(' ⏭ïļ Course already exists, skipping...'); + } console.log('✅ Database seeding completed!'); console.log('\n📊 Summary:'); diff --git a/Backend/src/controllers/AuthController.ts b/Backend/src/controllers/AuthController.ts index ddb7857c..1cc7c467 100644 --- a/Backend/src/controllers/AuthController.ts +++ b/Backend/src/controllers/AuthController.ts @@ -1,4 +1,4 @@ -import { Body, Post, Route, Tags, SuccessResponse, Response, Example } from 'tsoa'; +import { Body, Post, Route, Tags, SuccessResponse, Response, Example, Controller } from 'tsoa'; import { AuthService } from '../services/auth.service'; import { LoginRequest, @@ -18,7 +18,7 @@ export class AuthController { /** * User login - * @summary Login with username and password + * @summary Login with email and password * @param body Login credentials * @returns JWT token and user information */ @@ -52,7 +52,8 @@ export class AuthController { }) public async login(@Body() body: LoginRequest): Promise { // Validate input - const { error } = loginSchema.validate(body); + const { error, value } = loginSchema.validate(body); + if (error) { throw new ValidationError(error.details[0].message); } diff --git a/Backend/src/services/auth.service.ts b/Backend/src/services/auth.service.ts index 0d08d8ac..c29a3d86 100644 --- a/Backend/src/services/auth.service.ts +++ b/Backend/src/services/auth.service.ts @@ -19,34 +19,33 @@ export class AuthService { * User login */ async login(data: LoginRequest): Promise { - const { username, password } = data; + const { email, password } = data; // Find user with role and profile const user = await prisma.user.findUnique({ - where: { username }, + where: { email }, include: { role: true, profile: true } }); - if (!user) { - logger.warn('Login attempt with invalid username', { username }); - throw new UnauthorizedError('Invalid username or password'); + logger.warn('Login attempt with invalid email', { email }); + throw new UnauthorizedError('Invalid email or password'); } // Verify password const isPasswordValid = await bcrypt.compare(password, user.password); if (!isPasswordValid) { - logger.warn('Login attempt with invalid password', { username }); - throw new UnauthorizedError('Invalid username or password'); + logger.warn('Login attempt with invalid password', { email }); + throw new UnauthorizedError('Invalid email or password'); } // Generate tokens - const token = this.generateAccessToken(user.id, user.username, user.email, user.role.code); + const token = this.generateAccessToken(user.id, user.email, user.email, user.role.code); const refreshToken = this.generateRefreshToken(user.id); - logger.info('User logged in successfully', { userId: user.id, username: user.username }); + logger.info('User logged in successfully', { userId: user.id, email: user.email }); return { token, diff --git a/Backend/src/types/auth.types.ts b/Backend/src/types/auth.types.ts index fe50125a..569075b0 100644 --- a/Backend/src/types/auth.types.ts +++ b/Backend/src/types/auth.types.ts @@ -3,7 +3,7 @@ */ export interface LoginRequest { - username: string; + email: string; password: string; } @@ -17,6 +17,7 @@ export interface RegisterRequest { th?: string; en?: string; }; + phone: string; } export interface LoginResponse { diff --git a/Backend/src/validators/auth.validator.ts b/Backend/src/validators/auth.validator.ts index a62c582f..9cfe58ba 100644 --- a/Backend/src/validators/auth.validator.ts +++ b/Backend/src/validators/auth.validator.ts @@ -1,14 +1,12 @@ import Joi from 'joi'; export const loginSchema = Joi.object({ - username: Joi.string() - .min(3) - .max(50) + email: Joi.string() + .email({ tlds: { allow: false } }) // Allow any TLD including .local .required() .messages({ - 'string.min': 'Username must be at least 3 characters', - 'string.max': 'Username must not exceed 50 characters', - 'any.required': 'Username is required' + 'string.email': 'Please provide a valid email address', + 'any.required': 'Email is required' }), password: Joi.string() .min(6) @@ -47,6 +45,10 @@ export const registerSchema = Joi.object({ 'string.max': 'Password must not exceed 100 characters', 'any.required': 'Password is required' }), + prefix: Joi.object({ + th: Joi.string().optional(), + en: Joi.string().optional() + }).optional(), first_name: Joi.string() .min(1) .max(100) @@ -61,10 +63,13 @@ export const registerSchema = Joi.object({ .messages({ 'any.required': 'Last name is required' }), - prefix: Joi.object({ - th: Joi.string().optional(), - en: Joi.string().optional() - }).optional() + phone: Joi.string() + .min(10) + .max(15) + .required() + .messages({ + 'any.required': 'Phone number is required' + }) }); export const refreshTokenSchema = Joi.object({