create-craftjs 2.0.6 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/bin/index.js +10 -1
- package/package.json +5 -2
- package/template/Craft JS.postman_collection.json +1500 -0
- package/template/craft/commands/key-generate.js +12 -4
- package/template/craft/commands/make-controller.js +3 -3
- package/template/craft/commands/make-middleware.js +7 -7
- package/template/craft/commands/make-route.js +0 -3
- package/template/craft/commands/make-test.js +5 -4
- package/template/package-lock.json +95 -2
- package/template/package.json +3 -1
- package/template/src/config/database.ts +5 -3
- package/template/src/config/env.ts +10 -1
- package/template/src/config/logger.ts +2 -4
- package/template/src/config/redis.ts +77 -0
- package/template/src/config/web.ts +1 -1
- package/template/src/controllers/auth-controller.ts +22 -15
- package/template/src/controllers/user-controller.ts +1 -2
- package/template/src/database/seeders/seed.ts +14 -4
- package/template/src/dtos/auth-dto.ts +28 -0
- package/template/src/dtos/user-dto.ts +2 -5
- package/template/src/interfaces/auth-session.ts +16 -0
- package/template/src/interfaces/type-request.ts +11 -0
- package/template/src/main.ts +6 -4
- package/template/src/middleware/auth-middleware.ts +19 -28
- package/template/src/providers/auth-session-provider.ts +12 -0
- package/template/src/providers/db-auth-session.ts +23 -0
- package/template/src/providers/redis-auth-session.ts +33 -0
- package/template/src/repositories/auth-token-repository.ts +1 -1
- package/template/src/services/auth-service.ts +136 -78
- package/template/src/services/user-service.ts +8 -2
- package/template/test/user.test.ts +3 -2
- package/template/tsconfig.json +2 -1
- package/template/src/utils/type-request.ts +0 -6
|
@@ -14,13 +14,21 @@ function keyGenerate() {
|
|
|
14
14
|
|
|
15
15
|
const generateKey = (number) => crypto.randomBytes(number).toString("hex");
|
|
16
16
|
|
|
17
|
-
if (envContent.includes("
|
|
17
|
+
if (envContent.includes("nJWT_ACCESS_SECRET=")) {
|
|
18
18
|
envContent = envContent.replace(
|
|
19
|
-
/
|
|
20
|
-
`
|
|
19
|
+
/nJWT_ACCESS_SECRET=.*/g,
|
|
20
|
+
`nJWT_ACCESS_SECRET=${generateKey(16)}`
|
|
21
21
|
);
|
|
22
22
|
} else {
|
|
23
|
-
envContent += `\
|
|
23
|
+
envContent += `\nJWT_ACCESS_SECRET=${generateKey(16)}`;
|
|
24
|
+
}
|
|
25
|
+
if (envContent.includes("nJWT_REFRESH_SECRET=")) {
|
|
26
|
+
envContent = envContent.replace(
|
|
27
|
+
/nJWT_REFRESH_SECRET=.*/g,
|
|
28
|
+
`nJWT_REFRESH_SECRET=${generateKey(16)}`
|
|
29
|
+
);
|
|
30
|
+
} else {
|
|
31
|
+
envContent += `\nJWT_REFRESH_SECRET=${generateKey(16)}`;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
if (envContent.includes("APP_SECRET=")) {
|
|
@@ -29,7 +29,7 @@ function makeController(name, options = {}) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
const resourceMethods = `
|
|
32
|
-
static async
|
|
32
|
+
static async get(req: Request, res: Response, next: NextFunction) {
|
|
33
33
|
try {
|
|
34
34
|
res.status(200).json({ message: "Listing all resources" });
|
|
35
35
|
} catch (error) {
|
|
@@ -37,7 +37,7 @@ function makeController(name, options = {}) {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
static async
|
|
40
|
+
static async detail(req: Request, res: Response, next: NextFunction) {
|
|
41
41
|
try {
|
|
42
42
|
res.status(200).json({ message: "Showing single resource" });
|
|
43
43
|
} catch (error) {
|
|
@@ -71,7 +71,7 @@ function makeController(name, options = {}) {
|
|
|
71
71
|
`;
|
|
72
72
|
|
|
73
73
|
const defaultMethod = `
|
|
74
|
-
static async
|
|
74
|
+
static async get(req: Request, res: Response, next: NextFunction) {
|
|
75
75
|
try {
|
|
76
76
|
res.status(201).json({ message: "ok" });
|
|
77
77
|
} catch (error) {
|
|
@@ -30,14 +30,14 @@ function makeMiddleware(name) {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const content = `import { NextFunction, Request, Response } from "express";
|
|
33
|
-
|
|
34
|
-
export const
|
|
35
|
-
req: Request,
|
|
33
|
+
import { asyncHandler } from "@utils/async-handler";
|
|
34
|
+
export const authMiddleware = asyncHandler(
|
|
35
|
+
async (req: Request,
|
|
36
36
|
res: Response,
|
|
37
|
-
next: NextFunction
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
next: NextFunction) => {
|
|
38
|
+
next();
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
41
|
`;
|
|
42
42
|
|
|
43
43
|
fs.writeFileSync(filePath, content);
|
|
@@ -28,9 +28,6 @@ function makeRoute(name) {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const content = `import express from "express";
|
|
31
|
-
import { asyncHandler } from "../utils/async-handler";
|
|
32
|
-
import { authMiddleware } from "../middleware/auth-middleware";
|
|
33
|
-
|
|
34
31
|
import { ${className} } from "../controllers/${routeName}-controller";
|
|
35
32
|
|
|
36
33
|
export const ${routeConst} = express.Router();
|
|
@@ -21,12 +21,13 @@ function MakeTest(name) {
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
const template =
|
|
25
|
-
import
|
|
26
|
-
import {
|
|
24
|
+
const template = `/// <reference types="jest" />
|
|
25
|
+
import supertest from "supertest";
|
|
26
|
+
import { web } from "../src/config/web";
|
|
27
|
+
import { logger } from "../src/config/logger";
|
|
27
28
|
|
|
28
29
|
// example
|
|
29
|
-
describe("POST /api/
|
|
30
|
+
describe("POST /api/auth/register", () => {
|
|
30
31
|
it("should register new user", async () => {
|
|
31
32
|
const response = await supertest(web).post("/api/auth/register").send({
|
|
32
33
|
fullName: "test",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftjs",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "craftjs",
|
|
9
|
-
"version": "2.0
|
|
9
|
+
"version": "2.1.0",
|
|
10
10
|
"license": "UNLICENSED",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@prisma/adapter-mariadb": "^7.2.0",
|
|
@@ -23,12 +23,14 @@
|
|
|
23
23
|
"express-ejs-layouts": "^2.5.1",
|
|
24
24
|
"express-fileupload": "^1.5.1",
|
|
25
25
|
"inquirer": "^8.2.6",
|
|
26
|
+
"ioredis": "^5.9.3",
|
|
26
27
|
"jsonwebtoken": "^9.0.2",
|
|
27
28
|
"luxon": "^3.6.1",
|
|
28
29
|
"nodemailer": "^7.0.3",
|
|
29
30
|
"swagger-jsdoc": "^6.2.8",
|
|
30
31
|
"swagger-themes": "^1.4.3",
|
|
31
32
|
"swagger-ui-express": "^5.0.1",
|
|
33
|
+
"uuid": "^13.0.0",
|
|
32
34
|
"winston": "^3.17.0",
|
|
33
35
|
"winston-daily-rotate-file": "^5.0.0",
|
|
34
36
|
"yargs": "^17.7.2",
|
|
@@ -2448,6 +2450,12 @@
|
|
|
2448
2450
|
"hono": "^4"
|
|
2449
2451
|
}
|
|
2450
2452
|
},
|
|
2453
|
+
"node_modules/@ioredis/commands": {
|
|
2454
|
+
"version": "1.5.0",
|
|
2455
|
+
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.0.tgz",
|
|
2456
|
+
"integrity": "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==",
|
|
2457
|
+
"license": "MIT"
|
|
2458
|
+
},
|
|
2451
2459
|
"node_modules/@istanbuljs/load-nyc-config": {
|
|
2452
2460
|
"version": "1.1.0",
|
|
2453
2461
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
|
@@ -4525,6 +4533,15 @@
|
|
|
4525
4533
|
"node": ">=9"
|
|
4526
4534
|
}
|
|
4527
4535
|
},
|
|
4536
|
+
"node_modules/cluster-key-slot": {
|
|
4537
|
+
"version": "1.1.2",
|
|
4538
|
+
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
|
4539
|
+
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
|
4540
|
+
"license": "Apache-2.0",
|
|
4541
|
+
"engines": {
|
|
4542
|
+
"node": ">=0.10.0"
|
|
4543
|
+
}
|
|
4544
|
+
},
|
|
4528
4545
|
"node_modules/co": {
|
|
4529
4546
|
"version": "4.6.0",
|
|
4530
4547
|
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
|
@@ -6212,6 +6229,30 @@
|
|
|
6212
6229
|
"node": ">=8"
|
|
6213
6230
|
}
|
|
6214
6231
|
},
|
|
6232
|
+
"node_modules/ioredis": {
|
|
6233
|
+
"version": "5.9.3",
|
|
6234
|
+
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.9.3.tgz",
|
|
6235
|
+
"integrity": "sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==",
|
|
6236
|
+
"license": "MIT",
|
|
6237
|
+
"dependencies": {
|
|
6238
|
+
"@ioredis/commands": "1.5.0",
|
|
6239
|
+
"cluster-key-slot": "^1.1.0",
|
|
6240
|
+
"debug": "^4.3.4",
|
|
6241
|
+
"denque": "^2.1.0",
|
|
6242
|
+
"lodash.defaults": "^4.2.0",
|
|
6243
|
+
"lodash.isarguments": "^3.1.0",
|
|
6244
|
+
"redis-errors": "^1.2.0",
|
|
6245
|
+
"redis-parser": "^3.0.0",
|
|
6246
|
+
"standard-as-callback": "^2.1.0"
|
|
6247
|
+
},
|
|
6248
|
+
"engines": {
|
|
6249
|
+
"node": ">=12.22.0"
|
|
6250
|
+
},
|
|
6251
|
+
"funding": {
|
|
6252
|
+
"type": "opencollective",
|
|
6253
|
+
"url": "https://opencollective.com/ioredis"
|
|
6254
|
+
}
|
|
6255
|
+
},
|
|
6215
6256
|
"node_modules/ipaddr.js": {
|
|
6216
6257
|
"version": "1.9.1",
|
|
6217
6258
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
|
@@ -7235,6 +7276,12 @@
|
|
|
7235
7276
|
"dev": true,
|
|
7236
7277
|
"license": "MIT"
|
|
7237
7278
|
},
|
|
7279
|
+
"node_modules/lodash.defaults": {
|
|
7280
|
+
"version": "4.2.0",
|
|
7281
|
+
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
|
7282
|
+
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
|
7283
|
+
"license": "MIT"
|
|
7284
|
+
},
|
|
7238
7285
|
"node_modules/lodash.get": {
|
|
7239
7286
|
"version": "4.4.2",
|
|
7240
7287
|
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
|
@@ -7248,6 +7295,12 @@
|
|
|
7248
7295
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
|
7249
7296
|
"license": "MIT"
|
|
7250
7297
|
},
|
|
7298
|
+
"node_modules/lodash.isarguments": {
|
|
7299
|
+
"version": "3.1.0",
|
|
7300
|
+
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
|
7301
|
+
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
|
7302
|
+
"license": "MIT"
|
|
7303
|
+
},
|
|
7251
7304
|
"node_modules/lodash.isboolean": {
|
|
7252
7305
|
"version": "3.0.3",
|
|
7253
7306
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
|
@@ -8615,6 +8668,27 @@
|
|
|
8615
8668
|
"node": ">=8.10.0"
|
|
8616
8669
|
}
|
|
8617
8670
|
},
|
|
8671
|
+
"node_modules/redis-errors": {
|
|
8672
|
+
"version": "1.2.0",
|
|
8673
|
+
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
|
8674
|
+
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
|
8675
|
+
"license": "MIT",
|
|
8676
|
+
"engines": {
|
|
8677
|
+
"node": ">=4"
|
|
8678
|
+
}
|
|
8679
|
+
},
|
|
8680
|
+
"node_modules/redis-parser": {
|
|
8681
|
+
"version": "3.0.0",
|
|
8682
|
+
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
|
8683
|
+
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
|
8684
|
+
"license": "MIT",
|
|
8685
|
+
"dependencies": {
|
|
8686
|
+
"redis-errors": "^1.0.0"
|
|
8687
|
+
},
|
|
8688
|
+
"engines": {
|
|
8689
|
+
"node": ">=4"
|
|
8690
|
+
}
|
|
8691
|
+
},
|
|
8618
8692
|
"node_modules/regenerate": {
|
|
8619
8693
|
"version": "1.4.2",
|
|
8620
8694
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
|
@@ -9209,6 +9283,12 @@
|
|
|
9209
9283
|
"node": ">=10"
|
|
9210
9284
|
}
|
|
9211
9285
|
},
|
|
9286
|
+
"node_modules/standard-as-callback": {
|
|
9287
|
+
"version": "2.1.0",
|
|
9288
|
+
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
|
9289
|
+
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
|
9290
|
+
"license": "MIT"
|
|
9291
|
+
},
|
|
9212
9292
|
"node_modules/statuses": {
|
|
9213
9293
|
"version": "2.0.1",
|
|
9214
9294
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
|
@@ -9912,6 +9992,19 @@
|
|
|
9912
9992
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
|
9913
9993
|
"license": "MIT"
|
|
9914
9994
|
},
|
|
9995
|
+
"node_modules/uuid": {
|
|
9996
|
+
"version": "13.0.0",
|
|
9997
|
+
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
|
9998
|
+
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
|
9999
|
+
"funding": [
|
|
10000
|
+
"https://github.com/sponsors/broofa",
|
|
10001
|
+
"https://github.com/sponsors/ctavan"
|
|
10002
|
+
],
|
|
10003
|
+
"license": "MIT",
|
|
10004
|
+
"bin": {
|
|
10005
|
+
"uuid": "dist-node/bin/uuid"
|
|
10006
|
+
}
|
|
10007
|
+
},
|
|
9915
10008
|
"node_modules/v8-compile-cache-lib": {
|
|
9916
10009
|
"version": "3.0.1",
|
|
9917
10010
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
package/template/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "craftjs",
|
|
3
3
|
"description": "A starter kit backend framework powered by Express, TypeScript, EJS Engine, and Prisma — designed for rapid development, simplicity, and scalability.",
|
|
4
|
-
"version": "2.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"express",
|
|
7
7
|
"typescript",
|
|
@@ -53,12 +53,14 @@
|
|
|
53
53
|
"express-ejs-layouts": "^2.5.1",
|
|
54
54
|
"express-fileupload": "^1.5.1",
|
|
55
55
|
"inquirer": "^8.2.6",
|
|
56
|
+
"ioredis": "^5.9.3",
|
|
56
57
|
"jsonwebtoken": "^9.0.2",
|
|
57
58
|
"luxon": "^3.6.1",
|
|
58
59
|
"nodemailer": "^7.0.3",
|
|
59
60
|
"swagger-jsdoc": "^6.2.8",
|
|
60
61
|
"swagger-themes": "^1.4.3",
|
|
61
62
|
"swagger-ui-express": "^5.0.1",
|
|
63
|
+
"uuid": "^13.0.0",
|
|
62
64
|
"winston": "^3.17.0",
|
|
63
65
|
"winston-daily-rotate-file": "^5.0.0",
|
|
64
66
|
"yargs": "^17.7.2",
|
|
@@ -2,8 +2,7 @@ import { PrismaClient } from "@prisma/client";
|
|
|
2
2
|
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
|
3
3
|
import { logger } from "@config/logger";
|
|
4
4
|
import { dbLogger } from "@config/logger";
|
|
5
|
-
|
|
6
|
-
import { env } from "./env";
|
|
5
|
+
import { env } from "@config/env";
|
|
7
6
|
|
|
8
7
|
const adapter = new PrismaMariaDb({
|
|
9
8
|
host: env.DATABASE_HOST,
|
|
@@ -13,8 +12,8 @@ const adapter = new PrismaMariaDb({
|
|
|
13
12
|
connectionLimit: env.DATABASE_CONNECTION_LIMIT
|
|
14
13
|
? env.DATABASE_CONNECTION_LIMIT
|
|
15
14
|
: 5,
|
|
15
|
+
connectTimeout: 3000,
|
|
16
16
|
});
|
|
17
|
-
|
|
18
17
|
export const prismaClient = new PrismaClient({
|
|
19
18
|
adapter,
|
|
20
19
|
log: [
|
|
@@ -52,6 +51,9 @@ prismaClient.$on("error", (e) => {
|
|
|
52
51
|
export const connectDatabase = async () => {
|
|
53
52
|
try {
|
|
54
53
|
await prismaClient.$connect();
|
|
54
|
+
await prismaClient.$executeRawUnsafe(
|
|
55
|
+
"SELECT 1 FROM information_schema.tables LIMIT 1"
|
|
56
|
+
);
|
|
55
57
|
logger.info("✅ Connected Database");
|
|
56
58
|
} catch (error) {
|
|
57
59
|
logger.error("❌ Failed Connect To Database", error);
|
|
@@ -41,7 +41,8 @@ const envSchema = z.object({
|
|
|
41
41
|
),
|
|
42
42
|
PORT: z.coerce.number().default(4444),
|
|
43
43
|
COOKIE_ENCRYPTION_KEY: z.string(),
|
|
44
|
-
|
|
44
|
+
JWT_ACCESS_SECRET: z.string(),
|
|
45
|
+
JWT_REFRESH_SECRET: z.string(),
|
|
45
46
|
CLOUDINARY_CLOUD_NAME: z.string().optional(),
|
|
46
47
|
CLOUDINARY_API_KEY: z.string().optional(),
|
|
47
48
|
CLOUDINARY_API_SECRET: z.string().optional(),
|
|
@@ -50,6 +51,14 @@ const envSchema = z.object({
|
|
|
50
51
|
MAIL_USER: z.string().optional(),
|
|
51
52
|
MAIL_PASS: z.string().optional(),
|
|
52
53
|
MAIL_FROM: z.string().optional(),
|
|
54
|
+
REDIS_ENABLED: z
|
|
55
|
+
.enum(["true", "false"])
|
|
56
|
+
.default("false")
|
|
57
|
+
.transform((v) => v === "true"),
|
|
58
|
+
REDIS_HOST: z.string().optional().default("localhost"),
|
|
59
|
+
REDIS_PORT: z.coerce.number().default(6379),
|
|
60
|
+
REDIS_PASS: z.string().optional(),
|
|
61
|
+
REDIS_DB: z.coerce.number().default(0),
|
|
53
62
|
});
|
|
54
63
|
|
|
55
64
|
const _env = envSchema.safeParse(process.env);
|
|
@@ -59,13 +59,12 @@ export const dbLogger = winston.createLogger({
|
|
|
59
59
|
filename: path.join(logDir, "database-log-%DATE%.log"),
|
|
60
60
|
datePattern: "YYYY-MM-DD",
|
|
61
61
|
zippedArchive: false,
|
|
62
|
-
maxFiles: "
|
|
62
|
+
maxFiles: "7d",
|
|
63
63
|
maxSize: "10m",
|
|
64
64
|
}),
|
|
65
65
|
],
|
|
66
66
|
});
|
|
67
67
|
|
|
68
|
-
|
|
69
68
|
const plainFormat = winston.format.printf(({ timestamp, level, message }) => {
|
|
70
69
|
return `[${timestamp}] ${level.toUpperCase()}: ${message}`;
|
|
71
70
|
});
|
|
@@ -93,7 +92,6 @@ const coloredHttpFormat = winston.format.printf(
|
|
|
93
92
|
break;
|
|
94
93
|
}
|
|
95
94
|
|
|
96
|
-
|
|
97
95
|
const methodMatch = msg.match(/^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)/);
|
|
98
96
|
const methodColors = {
|
|
99
97
|
GET: chalk.green.bold("GET"),
|
|
@@ -122,7 +120,7 @@ export const httpAccessLogger = winston.createLogger({
|
|
|
122
120
|
filename: path.join(logDir, "access-log-%DATE%.log"),
|
|
123
121
|
datePattern: "YYYY-MM-DD",
|
|
124
122
|
zippedArchive: false,
|
|
125
|
-
maxFiles: "
|
|
123
|
+
maxFiles: "7d",
|
|
126
124
|
maxSize: "10m",
|
|
127
125
|
format: winston.format.combine(
|
|
128
126
|
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import Redis from "ioredis";
|
|
2
|
+
import { env } from "@config/env";
|
|
3
|
+
import { logger } from "@config/logger";
|
|
4
|
+
|
|
5
|
+
let redis: Redis | null = null;
|
|
6
|
+
let redisHealthy = false;
|
|
7
|
+
|
|
8
|
+
export const initRedis = async (): Promise<void> => {
|
|
9
|
+
if (!env.REDIS_ENABLED) {
|
|
10
|
+
logger.info("ℹ️ Redis disabled by configuration");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
logger.info("🔌 Connecting to Redis...");
|
|
16
|
+
redis = new Redis({
|
|
17
|
+
host: env.REDIS_HOST,
|
|
18
|
+
port: env.REDIS_PORT,
|
|
19
|
+
db: env.REDIS_DB,
|
|
20
|
+
password: env.REDIS_PASS || undefined,
|
|
21
|
+
enableOfflineQueue: false,
|
|
22
|
+
maxRetriesPerRequest: 1,
|
|
23
|
+
|
|
24
|
+
retryStrategy(times) {
|
|
25
|
+
if (times > 5) {
|
|
26
|
+
logger.error("❌ Redis unreachable after 5 retries");
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const delay = Math.min(times * 1000, 5000);
|
|
30
|
+
logger.warn(`Redis retrying connection (${times})...`);
|
|
31
|
+
return delay;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
await new Promise<void>((resolve, reject) => {
|
|
36
|
+
redis!.once("ready", () => {
|
|
37
|
+
redisHealthy = true;
|
|
38
|
+
logger.info(
|
|
39
|
+
`✅ Connected Redis (${env.REDIS_HOST}:${env.REDIS_PORT}, db ${env.REDIS_DB})`
|
|
40
|
+
);
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
redis!.once("error", (err) => {
|
|
45
|
+
redisHealthy = false;
|
|
46
|
+
logger.error("❌ Redis connection error", err);
|
|
47
|
+
reject(err);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
redis.on("error", (err) => {
|
|
52
|
+
redisHealthy = false;
|
|
53
|
+
logger.error("Redis runtime error", err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
redis.on("end", () => {
|
|
57
|
+
redisHealthy = false;
|
|
58
|
+
logger.warn("Redis connection closed");
|
|
59
|
+
});
|
|
60
|
+
} catch (error) {
|
|
61
|
+
redisHealthy = false;
|
|
62
|
+
logger.error("❌ Failed Connect To Redis", error);
|
|
63
|
+
|
|
64
|
+
redis = null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export const getRedis = (): Redis => {
|
|
69
|
+
if (!redis || !redisHealthy) {
|
|
70
|
+
throw new Error("Redis is not available");
|
|
71
|
+
}
|
|
72
|
+
return redis;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const isRedisHealthy = (): boolean => {
|
|
76
|
+
return env.REDIS_ENABLED && redisHealthy;
|
|
77
|
+
};
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { NextFunction, Request, Response } from "express";
|
|
2
|
-
import {
|
|
3
|
-
loginRequest,
|
|
4
|
-
CreateUserRequest,
|
|
5
|
-
UpdateUserRequest,
|
|
6
|
-
} from "@dtos/user-dto";
|
|
2
|
+
import { CreateUserRequest, UpdateUserRequest } from "@dtos/user-dto";
|
|
7
3
|
import { successResponse } from "@utils/response";
|
|
8
4
|
import { AuthService } from "@services/auth-service";
|
|
9
|
-
import { UserRequest } from "@
|
|
5
|
+
import { UserRequest } from "@interfaces/type-request";
|
|
10
6
|
import { env } from "@config/env";
|
|
7
|
+
import {
|
|
8
|
+
AuthUpdateRequest,
|
|
9
|
+
LoginRequest,
|
|
10
|
+
RegisterRequest,
|
|
11
|
+
} from "@dtos/auth-dto";
|
|
11
12
|
|
|
12
13
|
export class AuthController {
|
|
13
14
|
static async register(req: Request, res: Response, next: NextFunction) {
|
|
14
15
|
try {
|
|
15
|
-
const request:
|
|
16
|
+
const request: RegisterRequest = req.body as RegisterRequest;
|
|
16
17
|
const response = await AuthService.register(request);
|
|
17
18
|
res.status(201).json(successResponse("Register Berhasil", 201, response));
|
|
18
19
|
} catch (error) {
|
|
@@ -22,7 +23,7 @@ export class AuthController {
|
|
|
22
23
|
|
|
23
24
|
static async login(req: Request, res: Response, next: NextFunction) {
|
|
24
25
|
try {
|
|
25
|
-
const request:
|
|
26
|
+
const request: LoginRequest = req.body as LoginRequest;
|
|
26
27
|
const response = await AuthService.login(request);
|
|
27
28
|
res.cookie("refresh_token", response.refreshToken, {
|
|
28
29
|
httpOnly: true,
|
|
@@ -33,7 +34,7 @@ export class AuthController {
|
|
|
33
34
|
});
|
|
34
35
|
res.status(200).json(
|
|
35
36
|
successResponse("Login Berhasil", 200, {
|
|
36
|
-
user: response.
|
|
37
|
+
user: response.dataUser,
|
|
37
38
|
accessToken: response.accessToken,
|
|
38
39
|
})
|
|
39
40
|
);
|
|
@@ -44,7 +45,7 @@ export class AuthController {
|
|
|
44
45
|
|
|
45
46
|
static async me(req: UserRequest, res: Response, next: NextFunction) {
|
|
46
47
|
try {
|
|
47
|
-
const response = await AuthService.me(req
|
|
48
|
+
const response = await AuthService.me(req);
|
|
48
49
|
res
|
|
49
50
|
.status(200)
|
|
50
51
|
.json(successResponse("Get Detail User Berhasil", 200, response));
|
|
@@ -59,11 +60,17 @@ export class AuthController {
|
|
|
59
60
|
next: NextFunction
|
|
60
61
|
) {
|
|
61
62
|
try {
|
|
62
|
-
const request:
|
|
63
|
-
const response = await AuthService.updateProfile(req
|
|
64
|
-
|
|
65
|
-
.status(200)
|
|
66
|
-
|
|
63
|
+
const request: AuthUpdateRequest = req.body as AuthUpdateRequest;
|
|
64
|
+
const response = await AuthService.updateProfile(req, request);
|
|
65
|
+
if (response.rotated) {
|
|
66
|
+
res.status(200).json(
|
|
67
|
+
successResponse("Update User Berhasil", 200, {
|
|
68
|
+
accessToken: response.accessToken,
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
res.status(200).json(successResponse("Update User Berhasil", 200));
|
|
67
74
|
} catch (error) {
|
|
68
75
|
next(error);
|
|
69
76
|
}
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
} from "@dtos/user-dto";
|
|
7
7
|
import { UserService } from "@services/user-service";
|
|
8
8
|
import { successResponse, paginateResponse } from "@utils/response";
|
|
9
|
-
import { UserRequest } from "@utils/type-request";
|
|
10
9
|
import { env } from "@config/env";
|
|
11
10
|
export class UserController {
|
|
12
11
|
static async get(req: Request, res: Response, next: NextFunction) {
|
|
@@ -58,7 +57,7 @@ export class UserController {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
static async update(req:
|
|
60
|
+
static async update(req: Request, res: Response, next: NextFunction) {
|
|
62
61
|
try {
|
|
63
62
|
const id = req.params.id;
|
|
64
63
|
const request: UpdateUserRequest = req.body as UpdateUserRequest;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { prismaClient } from "
|
|
1
|
+
import { prismaClient } from "@config/database";
|
|
2
2
|
import * as argon2 from "argon2";
|
|
3
3
|
import dotenv from "dotenv";
|
|
4
4
|
|
|
@@ -6,11 +6,21 @@ dotenv.config();
|
|
|
6
6
|
|
|
7
7
|
async function main() {
|
|
8
8
|
await prismaClient.user.upsert({
|
|
9
|
-
where: { email: "
|
|
9
|
+
where: { email: "johndoe@gmail.com" },
|
|
10
10
|
update: {},
|
|
11
11
|
create: {
|
|
12
|
-
full_name: "
|
|
13
|
-
email: "
|
|
12
|
+
full_name: "John Doe",
|
|
13
|
+
email: "johndoe@gmail.com",
|
|
14
|
+
password: await argon2.hash("123456"),
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await prismaClient.user.upsert({
|
|
19
|
+
where: { email: "janedoe@gmail.com" },
|
|
20
|
+
update: {},
|
|
21
|
+
create: {
|
|
22
|
+
full_name: "Jane Doe",
|
|
23
|
+
email: "janedoe@gmail.com",
|
|
14
24
|
password: await argon2.hash("123456"),
|
|
15
25
|
},
|
|
16
26
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IUser } from "@interfaces/type-request";
|
|
2
|
+
export type LoginRequest = {
|
|
3
|
+
email: string;
|
|
4
|
+
password: string;
|
|
5
|
+
};
|
|
6
|
+
export type RegisterRequest = {
|
|
7
|
+
full_name: string;
|
|
8
|
+
email: string;
|
|
9
|
+
password: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type AuthUpdateRequest = {
|
|
13
|
+
full_name: string;
|
|
14
|
+
email?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AuthMeResponse = {
|
|
18
|
+
id: string;
|
|
19
|
+
full_name: string;
|
|
20
|
+
email: string;
|
|
21
|
+
};
|
|
22
|
+
export function toAuthMeResponse(user: IUser): AuthMeResponse {
|
|
23
|
+
return {
|
|
24
|
+
id: user.user_id,
|
|
25
|
+
full_name: user.user_full_name,
|
|
26
|
+
email: user.user_email,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { User } from "@prisma/client";
|
|
2
|
-
import { formatTime } from "
|
|
3
|
-
export type loginRequest = {
|
|
4
|
-
email: string;
|
|
5
|
-
password: string;
|
|
6
|
-
};
|
|
2
|
+
import { formatTime } from "@utils/formatTime";
|
|
7
3
|
export type CreateUserRequest = {
|
|
8
4
|
full_name: string;
|
|
9
5
|
email: string;
|
|
@@ -13,6 +9,7 @@ export type CreateUserRequest = {
|
|
|
13
9
|
export type UpdateUserRequest = {
|
|
14
10
|
full_name: string;
|
|
15
11
|
email?: string;
|
|
12
|
+
password?: string;
|
|
16
13
|
};
|
|
17
14
|
|
|
18
15
|
export type ListUserRequest = {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface AuthSession {
|
|
2
|
+
user_id: string;
|
|
3
|
+
user_email: string;
|
|
4
|
+
user_full_name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface AuthSessionProvider {
|
|
8
|
+
get(userId: string, jti: string): Promise<AuthSession | null>;
|
|
9
|
+
set(
|
|
10
|
+
userId: string,
|
|
11
|
+
jti: string,
|
|
12
|
+
data: AuthSession,
|
|
13
|
+
ttl: number
|
|
14
|
+
): Promise<void>;
|
|
15
|
+
delete(userId: string, jti: string): Promise<void>;
|
|
16
|
+
}
|