create-craftjs 1.0.0 → 1.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-craftjs",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A starter kit backend framework powered by Express, TypeScript, EJS Engine, and Prisma — designed for rapid development, simplicity, and scalability.",
5
5
  "bin": {
6
6
  "create-craftjs": "bin/index.js"
@@ -12,6 +12,7 @@
12
12
  "@prisma/client": "^6.6.0",
13
13
  "argon2": "^0.41.1",
14
14
  "chalk": "^4.1.2",
15
+ "cloudinary": "^2.6.1",
15
16
  "cookie-parser": "^1.4.7",
16
17
  "cors": "^2.8.5",
17
18
  "crypto": "^1.0.1",
@@ -19,9 +20,11 @@
19
20
  "ejs": "^3.1.10",
20
21
  "express": "^5.1.0",
21
22
  "express-ejs-layouts": "^2.5.1",
23
+ "express-fileupload": "^1.5.1",
22
24
  "inquirer": "^8.2.6",
23
25
  "jsonwebtoken": "^9.0.2",
24
26
  "luxon": "^3.6.1",
27
+ "nodemailer": "^7.0.3",
25
28
  "swagger-jsdoc": "^6.2.8",
26
29
  "swagger-themes": "^1.4.3",
27
30
  "swagger-ui-express": "^5.0.1",
@@ -41,10 +44,12 @@
41
44
  "@types/ejs": "^3.1.5",
42
45
  "@types/express": "^5.0.1",
43
46
  "@types/express-ejs-layouts": "^2.5.4",
47
+ "@types/express-fileupload": "^1.5.1",
44
48
  "@types/jest": "^29.5.14",
45
49
  "@types/jsonwebtoken": "^9.0.9",
46
50
  "@types/luxon": "^3.6.2",
47
51
  "@types/node": "^22.15.18",
52
+ "@types/nodemailer": "^6.4.17",
48
53
  "@types/supertest": "^6.0.3",
49
54
  "@types/swagger-jsdoc": "^6.0.4",
50
55
  "@types/swagger-ui-express": "^4.1.8",
@@ -3009,6 +3014,16 @@
3009
3014
  "@types/node": "*"
3010
3015
  }
3011
3016
  },
3017
+ "node_modules/@types/busboy": {
3018
+ "version": "1.5.4",
3019
+ "resolved": "https://registry.npmjs.org/@types/busboy/-/busboy-1.5.4.tgz",
3020
+ "integrity": "sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==",
3021
+ "dev": true,
3022
+ "license": "MIT",
3023
+ "dependencies": {
3024
+ "@types/node": "*"
3025
+ }
3026
+ },
3012
3027
  "node_modules/@types/connect": {
3013
3028
  "version": "3.4.38",
3014
3029
  "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@@ -3086,6 +3101,17 @@
3086
3101
  "@types/node": "*"
3087
3102
  }
3088
3103
  },
3104
+ "node_modules/@types/express-fileupload": {
3105
+ "version": "1.5.1",
3106
+ "resolved": "https://registry.npmjs.org/@types/express-fileupload/-/express-fileupload-1.5.1.tgz",
3107
+ "integrity": "sha512-DllImBVI1lCyjl2klky/TEwk60mbNebgXv1669h66g9TfptWSrEFq5a/raHSutaFzjSm1tmn9ypdNfu4jPSixQ==",
3108
+ "dev": true,
3109
+ "license": "MIT",
3110
+ "dependencies": {
3111
+ "@types/busboy": "*",
3112
+ "@types/express": "*"
3113
+ }
3114
+ },
3089
3115
  "node_modules/@types/express-serve-static-core": {
3090
3116
  "version": "5.0.6",
3091
3117
  "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz",
@@ -3209,6 +3235,16 @@
3209
3235
  "undici-types": "~6.21.0"
3210
3236
  }
3211
3237
  },
3238
+ "node_modules/@types/nodemailer": {
3239
+ "version": "6.4.17",
3240
+ "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz",
3241
+ "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==",
3242
+ "dev": true,
3243
+ "license": "MIT",
3244
+ "dependencies": {
3245
+ "@types/node": "*"
3246
+ }
3247
+ },
3212
3248
  "node_modules/@types/qs": {
3213
3249
  "version": "6.9.18",
3214
3250
  "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz",
@@ -3787,6 +3823,17 @@
3787
3823
  "dev": true,
3788
3824
  "license": "MIT"
3789
3825
  },
3826
+ "node_modules/busboy": {
3827
+ "version": "1.6.0",
3828
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
3829
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
3830
+ "dependencies": {
3831
+ "streamsearch": "^1.1.0"
3832
+ },
3833
+ "engines": {
3834
+ "node": ">=10.16.0"
3835
+ }
3836
+ },
3790
3837
  "node_modules/bytes": {
3791
3838
  "version": "3.1.2",
3792
3839
  "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -4041,6 +4088,19 @@
4041
4088
  "node": ">=0.8"
4042
4089
  }
4043
4090
  },
4091
+ "node_modules/cloudinary": {
4092
+ "version": "2.6.1",
4093
+ "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.6.1.tgz",
4094
+ "integrity": "sha512-Dt7o3p4VzxYoTi+EqWkVQmGy6WiXIyMcG5Gbr9kPR/EQ+jZa+3FFzlDKfDx1uDsaB1aTR1gYeO6wZqrgLFaByQ==",
4095
+ "license": "MIT",
4096
+ "dependencies": {
4097
+ "lodash": "^4.17.21",
4098
+ "q": "^1.5.1"
4099
+ },
4100
+ "engines": {
4101
+ "node": ">=9"
4102
+ }
4103
+ },
4044
4104
  "node_modules/co": {
4045
4105
  "version": "4.6.0",
4046
4106
  "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -4757,6 +4817,18 @@
4757
4817
  "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz",
4758
4818
  "integrity": "sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA=="
4759
4819
  },
4820
+ "node_modules/express-fileupload": {
4821
+ "version": "1.5.1",
4822
+ "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-1.5.1.tgz",
4823
+ "integrity": "sha512-LsYG1ALXEB7vlmjuSw8ABeOctMp8a31aUC5ZF55zuz7O2jLFnmJYrCv10py357ky48aEoBQ/9bVXgFynjvaPmA==",
4824
+ "license": "MIT",
4825
+ "dependencies": {
4826
+ "busboy": "^1.6.0"
4827
+ },
4828
+ "engines": {
4829
+ "node": ">=12.0.0"
4830
+ }
4831
+ },
4760
4832
  "node_modules/express/node_modules/cookie-signature": {
4761
4833
  "version": "1.2.2",
4762
4834
  "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
@@ -6797,6 +6869,15 @@
6797
6869
  "dev": true,
6798
6870
  "license": "MIT"
6799
6871
  },
6872
+ "node_modules/nodemailer": {
6873
+ "version": "7.0.3",
6874
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
6875
+ "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
6876
+ "license": "MIT-0",
6877
+ "engines": {
6878
+ "node": ">=6.0.0"
6879
+ }
6880
+ },
6800
6881
  "node_modules/nodemon": {
6801
6882
  "version": "3.1.10",
6802
6883
  "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
@@ -7278,6 +7359,17 @@
7278
7359
  ],
7279
7360
  "license": "MIT"
7280
7361
  },
7362
+ "node_modules/q": {
7363
+ "version": "1.5.1",
7364
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
7365
+ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
7366
+ "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
7367
+ "license": "MIT",
7368
+ "engines": {
7369
+ "node": ">=0.6.0",
7370
+ "teleport": ">=0.2.0"
7371
+ }
7372
+ },
7281
7373
  "node_modules/qs": {
7282
7374
  "version": "6.14.0",
7283
7375
  "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -7848,6 +7940,14 @@
7848
7940
  "node": ">= 0.8"
7849
7941
  }
7850
7942
  },
7943
+ "node_modules/streamsearch": {
7944
+ "version": "1.1.0",
7945
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
7946
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
7947
+ "engines": {
7948
+ "node": ">=10.0.0"
7949
+ }
7950
+ },
7851
7951
  "node_modules/string_decoder": {
7852
7952
  "version": "1.3.0",
7853
7953
  "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -29,6 +29,7 @@
29
29
  "@prisma/client": "^6.6.0",
30
30
  "argon2": "^0.41.1",
31
31
  "chalk": "^4.1.2",
32
+ "cloudinary": "^2.6.1",
32
33
  "cookie-parser": "^1.4.7",
33
34
  "cors": "^2.8.5",
34
35
  "crypto": "^1.0.1",
@@ -36,9 +37,11 @@
36
37
  "ejs": "^3.1.10",
37
38
  "express": "^5.1.0",
38
39
  "express-ejs-layouts": "^2.5.1",
40
+ "express-fileupload": "^1.5.1",
39
41
  "inquirer": "^8.2.6",
40
42
  "jsonwebtoken": "^9.0.2",
41
43
  "luxon": "^3.6.1",
44
+ "nodemailer": "^7.0.3",
42
45
  "swagger-jsdoc": "^6.2.8",
43
46
  "swagger-themes": "^1.4.3",
44
47
  "swagger-ui-express": "^5.0.1",
@@ -58,10 +61,12 @@
58
61
  "@types/ejs": "^3.1.5",
59
62
  "@types/express": "^5.0.1",
60
63
  "@types/express-ejs-layouts": "^2.5.4",
64
+ "@types/express-fileupload": "^1.5.1",
61
65
  "@types/jest": "^29.5.14",
62
66
  "@types/jsonwebtoken": "^9.0.9",
63
67
  "@types/luxon": "^3.6.2",
64
68
  "@types/node": "^22.15.18",
69
+ "@types/nodemailer": "^6.4.17",
65
70
  "@types/supertest": "^6.0.3",
66
71
  "@types/swagger-jsdoc": "^6.0.4",
67
72
  "@types/swagger-ui-express": "^4.1.8",
@@ -0,0 +1,21 @@
1
+ import { v2 as cloudinary } from "cloudinary";
2
+ import { env } from "./env";
3
+ import { logger } from "./logger";
4
+
5
+ if (
6
+ env.CLOUDINARY_CLOUD_NAME &&
7
+ env.CLOUDINARY_API_KEY &&
8
+ env.CLOUDINARY_API_SECRET
9
+ ) {
10
+ cloudinary.config({
11
+ cloud_name: env.CLOUDINARY_CLOUD_NAME,
12
+ api_key: env.CLOUDINARY_API_KEY,
13
+ api_secret: env.CLOUDINARY_API_SECRET,
14
+ });
15
+ } else {
16
+ logger.warn(
17
+ "⚠️ Cloudinary config is incomplete. Skipping Cloudinary configuration."
18
+ );
19
+ }
20
+
21
+ export { cloudinary };
@@ -14,9 +14,36 @@ const envSchema = z.object({
14
14
  .url({ message: "DATABASE_URL harus URL yang valid" }),
15
15
  BASE_URL: z.string().url(),
16
16
  BASE_API_URL: z.string().url(),
17
- CLIENT_URL: z.string().url().optional(),
17
+ CLIENT_URL: z
18
+ .string()
19
+ .optional()
20
+ .refine(
21
+ (val) => {
22
+ if (!val) return true;
23
+ return val.split(",").every((url) => {
24
+ try {
25
+ new URL(url.trim());
26
+ return true;
27
+ } catch {
28
+ return false;
29
+ }
30
+ });
31
+ },
32
+ {
33
+ message:
34
+ "CLIENT_URL harus berupa URL valid, dipisah koma jika lebih dari satu",
35
+ }
36
+ ),
18
37
  PORT: z.coerce.number().default(3000),
19
38
  JWT_SECRET: z.string(),
39
+ CLOUDINARY_CLOUD_NAME: z.string().optional(),
40
+ CLOUDINARY_API_KEY: z.string().optional(),
41
+ CLOUDINARY_API_SECRET: z.string().optional(),
42
+ MAIL_HOST: z.string().optional(),
43
+ MAIL_PORT: z.string().optional().default("587"),
44
+ MAIL_USER: z.string().optional(),
45
+ MAIL_PASS: z.string().optional(),
46
+ MAIL_FROM: z.string().optional(),
20
47
  });
21
48
 
22
49
  const _env = envSchema.safeParse(process.env);
@@ -26,4 +53,15 @@ if (!_env.success) {
26
53
  process.exit(1);
27
54
  }
28
55
 
29
- export const env = _env.data;
56
+ export const parsed = _env.data;
57
+ export const env = {
58
+ ...parsed,
59
+ CLIENT_URLS: parsed.CLIENT_URL
60
+ ? parsed.CLIENT_URL.split(",").map((url) => url.trim())
61
+ : [
62
+ "http://localhost:8000",
63
+ "http://localhost:5173",
64
+ "http://localhost:3333",
65
+ "http://localhost:3000",
66
+ ],
67
+ };
@@ -70,25 +70,48 @@ const plainFormat = winston.format.printf(({ timestamp, level, message }) => {
70
70
  return `[${timestamp}] ${level.toUpperCase()}: ${message}`;
71
71
  });
72
72
 
73
- // Format untuk CONSOLE logs — dengan warna
74
73
  const coloredHttpFormat = winston.format.printf(
75
74
  ({ timestamp, level, message }) => {
75
+ const msg = String(message);
76
+
76
77
  let coloredLevel = level.toUpperCase();
77
78
  switch (level) {
78
79
  case "error":
79
- coloredLevel = chalk.red.bold(level.toUpperCase());
80
+ coloredLevel = chalk.redBright.bold(level.toUpperCase());
80
81
  break;
81
82
  case "warn":
82
- coloredLevel = chalk.yellow.bold(level.toUpperCase());
83
+ coloredLevel = chalk.yellowBright.bold(level.toUpperCase());
83
84
  break;
84
85
  case "info":
85
- coloredLevel = chalk.blue.bold(level.toUpperCase());
86
+ coloredLevel = chalk.greenBright.bold(level.toUpperCase());
87
+ break;
88
+ case "debug":
89
+ coloredLevel = chalk.blueBright.bold(level.toUpperCase());
86
90
  break;
87
91
  default:
88
- coloredLevel = chalk.white(level.toUpperCase());
92
+ coloredLevel = chalk.whiteBright(level.toUpperCase());
89
93
  break;
90
94
  }
91
- return `[${chalk.gray(timestamp)}] ${coloredLevel}: ${message}`;
95
+
96
+ // Pewarnaan method HTTP
97
+ const methodMatch = msg.match(/^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)/);
98
+ const methodColors = {
99
+ GET: chalk.green.bold("GET"),
100
+ POST: chalk.cyan.bold("POST"),
101
+ PUT: chalk.yellow.bold("PUT"),
102
+ DELETE: chalk.red.bold("DELETE"),
103
+ PATCH: chalk.magenta.bold("PATCH"),
104
+ OPTIONS: chalk.white.bold("OPTIONS"),
105
+ HEAD: chalk.gray.bold("HEAD"),
106
+ } as const;
107
+
108
+ let coloredMessage = msg;
109
+ if (methodMatch) {
110
+ const method = methodMatch[0] as keyof typeof methodColors;
111
+ coloredMessage = msg.replace(method, methodColors[method]);
112
+ }
113
+
114
+ return `[${chalk.gray(timestamp)}] ${coloredLevel}: ${coloredMessage}`;
92
115
  }
93
116
  );
94
117
 
@@ -0,0 +1,23 @@
1
+ import nodemailer from "nodemailer";
2
+ import { logger } from "../config/logger";
3
+ import { env } from "../config/env";
4
+
5
+ const transporter = nodemailer.createTransport({
6
+ host: env.MAIL_HOST,
7
+ port: Number(env.MAIL_PORT),
8
+ auth: {
9
+ user: env.MAIL_USER,
10
+ pass: env.MAIL_PASS,
11
+ },
12
+ // logger: true
13
+ });
14
+
15
+ transporter.verify((error, success) => {
16
+ if (error) {
17
+ logger.error("❌ Failed Connect To Nodemailer", error);
18
+ } else {
19
+ logger.info("✅ Connected To Nodemailer", success);
20
+ }
21
+ });
22
+
23
+ export default transporter;
@@ -1,4 +1,5 @@
1
1
  import express from "express";
2
+ import fileUpload from "express-fileupload";
2
3
  import cors from "cors";
3
4
  import cookieParser from "cookie-parser";
4
5
  import { env } from "./env";
@@ -21,7 +22,13 @@ export const web = express();
21
22
  // Middleware
22
23
  web.use(express.json());
23
24
  web.use(cookieParser());
24
- web.use(cors({ credentials: true, origin: `${env.CLIENT_URL}` }));
25
+ web.use(
26
+ cors({
27
+ origin: env.CLIENT_URLS,
28
+ credentials: true,
29
+ })
30
+ );
31
+ web.use(fileUpload({ useTempFiles: true, tempFileDir: "./temp/" }));
25
32
  web.use(express.static("public"));
26
33
  web.use(httpLogger);
27
34
 
Binary file