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.
Files changed (34) hide show
  1. package/README.md +3 -0
  2. package/bin/index.js +10 -1
  3. package/package.json +5 -2
  4. package/template/Craft JS.postman_collection.json +1500 -0
  5. package/template/craft/commands/key-generate.js +12 -4
  6. package/template/craft/commands/make-controller.js +3 -3
  7. package/template/craft/commands/make-middleware.js +7 -7
  8. package/template/craft/commands/make-route.js +0 -3
  9. package/template/craft/commands/make-test.js +5 -4
  10. package/template/package-lock.json +95 -2
  11. package/template/package.json +3 -1
  12. package/template/src/config/database.ts +5 -3
  13. package/template/src/config/env.ts +10 -1
  14. package/template/src/config/logger.ts +2 -4
  15. package/template/src/config/redis.ts +77 -0
  16. package/template/src/config/web.ts +1 -1
  17. package/template/src/controllers/auth-controller.ts +22 -15
  18. package/template/src/controllers/user-controller.ts +1 -2
  19. package/template/src/database/seeders/seed.ts +14 -4
  20. package/template/src/dtos/auth-dto.ts +28 -0
  21. package/template/src/dtos/user-dto.ts +2 -5
  22. package/template/src/interfaces/auth-session.ts +16 -0
  23. package/template/src/interfaces/type-request.ts +11 -0
  24. package/template/src/main.ts +6 -4
  25. package/template/src/middleware/auth-middleware.ts +19 -28
  26. package/template/src/providers/auth-session-provider.ts +12 -0
  27. package/template/src/providers/db-auth-session.ts +23 -0
  28. package/template/src/providers/redis-auth-session.ts +33 -0
  29. package/template/src/repositories/auth-token-repository.ts +1 -1
  30. package/template/src/services/auth-service.ts +136 -78
  31. package/template/src/services/user-service.ts +8 -2
  32. package/template/test/user.test.ts +3 -2
  33. package/template/tsconfig.json +2 -1
  34. 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("JWT_SECRET=")) {
17
+ if (envContent.includes("nJWT_ACCESS_SECRET=")) {
18
18
  envContent = envContent.replace(
19
- /JWT_SECRET=.*/g,
20
- `JWT_SECRET=${generateKey(16)}`
19
+ /nJWT_ACCESS_SECRET=.*/g,
20
+ `nJWT_ACCESS_SECRET=${generateKey(16)}`
21
21
  );
22
22
  } else {
23
- envContent += `\nJWT_SECRET_ACCESS_TOKEN=${generateKey(16)}`;
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 getAll(req: Request, res: Response, next: NextFunction) {
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 getOne(req: Request, res: Response, next: NextFunction) {
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 getAll(req: Request, res: Response, next: NextFunction) {
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 ${className} = async (
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
- next();
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 = `import supertest from "supertest";
25
- import { web } from "../src/application/web";
26
- import { logger } from "../src/application/logging";
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/users", () => {
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.6",
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.6",
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",
@@ -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.6",
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
- JWT_SECRET: z.string(),
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: "30d",
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: "30d",
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
+ };
@@ -43,7 +43,7 @@ web.use(mainRouter);
43
43
 
44
44
  // 404 Handler
45
45
  web.use((req, res) => {
46
- res.status(404).json(errorResponse("Request Tidak Ada", 404));
46
+ res.status(404).json(errorResponse("Route Tidak Ada", 404));
47
47
  });
48
48
 
49
49
  // Global Error Handler
@@ -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 "@utils/type-request";
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: CreateUserRequest = req.body as CreateUserRequest;
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: loginRequest = req.body as loginRequest;
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.user,
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.user!);
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: UpdateUserRequest = req.body as UpdateUserRequest;
63
- const response = await AuthService.updateProfile(req.user!, request);
64
- res
65
- .status(200)
66
- .json(successResponse("Update User Berhasil", 200, response));
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: UserRequest, res: Response, next: NextFunction) {
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 "../../config/database";
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: "tes@gmail.com" },
9
+ where: { email: "johndoe@gmail.com" },
10
10
  update: {},
11
11
  create: {
12
- full_name: "Akun Test",
13
- email: "tes@gmail.com",
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 "../utils/formatTime";
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
+ }