create-charcole 2.0.4 → 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 (75) hide show
  1. package/CHANGELOG.md +200 -14
  2. package/README.md +137 -332
  3. package/bin/index.js +236 -55
  4. package/bin/lib/pkgManager.js +8 -25
  5. package/bin/lib/templateHandler.js +5 -42
  6. package/package.json +2 -2
  7. package/plans/V2_1_PLAN.md +20 -0
  8. package/template/js/.env.example +8 -0
  9. package/template/js/basePackage.json +11 -13
  10. package/template/js/src/app.js +4 -1
  11. package/template/js/src/modules/auth/auth.constants.js +3 -0
  12. package/template/js/src/modules/auth/auth.controller.js +29 -0
  13. package/template/js/src/modules/auth/auth.middlewares.js +19 -0
  14. package/template/js/src/modules/auth/auth.routes.js +9 -0
  15. package/template/js/src/modules/auth/auth.schemas.js +60 -0
  16. package/template/js/src/modules/auth/auth.service.js +67 -0
  17. package/template/js/src/modules/auth/package.json +6 -0
  18. package/template/js/src/repositories/user.repo.js +19 -0
  19. package/template/js/src/routes/index.js +25 -0
  20. package/template/js/src/routes/protected.js +13 -0
  21. package/template/ts/.env.example +8 -0
  22. package/template/ts/basePackage.json +19 -15
  23. package/template/ts/build.js +46 -0
  24. package/template/ts/src/app.ts +5 -4
  25. package/template/ts/src/middlewares/errorHandler.ts +15 -23
  26. package/template/ts/src/middlewares/requestLogger.ts +1 -1
  27. package/template/ts/src/middlewares/validateRequest.ts +1 -1
  28. package/template/ts/src/modules/auth/auth.constants.ts +6 -0
  29. package/template/ts/src/modules/auth/auth.controller.ts +32 -0
  30. package/template/ts/src/modules/auth/auth.middlewares.ts +46 -0
  31. package/template/ts/src/modules/auth/auth.routes.ts +9 -0
  32. package/template/ts/src/modules/auth/auth.schemas.ts +73 -0
  33. package/template/ts/src/modules/auth/auth.service.ts +106 -0
  34. package/template/ts/src/modules/auth/package.json +10 -0
  35. package/template/ts/src/modules/health/controller.ts +3 -3
  36. package/template/ts/src/repositories/user.repo.ts +33 -0
  37. package/template/ts/src/routes/index.ts +24 -0
  38. package/template/ts/src/routes/protected.ts +13 -0
  39. package/template/ts/src/server.ts +0 -1
  40. package/template/ts/src/utils/logger.ts +1 -1
  41. package/template/ts/tsconfig.json +14 -7
  42. package/template/js/ARCHITECTURE_DIAGRAMS.md +0 -283
  43. package/template/js/CHECKLIST.md +0 -279
  44. package/template/js/COMPLETE.md +0 -405
  45. package/template/js/ERROR_HANDLING.md +0 -393
  46. package/template/js/IMPLEMENTATION.md +0 -368
  47. package/template/js/IMPLEMENTATION_COMPLETE.md +0 -363
  48. package/template/js/INDEX.md +0 -290
  49. package/template/js/QUICK_REFERENCE.md +0 -270
  50. package/template/js/package.json +0 -28
  51. package/template/js/src/routes.js +0 -17
  52. package/template/js/test-api.js +0 -100
  53. package/template/ts/ARCHITECTURE_DIAGRAMS.md +0 -283
  54. package/template/ts/CHECKLIST.md +0 -279
  55. package/template/ts/COMPLETE.md +0 -405
  56. package/template/ts/ERROR_HANDLING.md +0 -393
  57. package/template/ts/IMPLEMENTATION.md +0 -368
  58. package/template/ts/IMPLEMENTATION_COMPLETE.md +0 -363
  59. package/template/ts/INDEX.md +0 -290
  60. package/template/ts/QUICK_REFERENCE.md +0 -270
  61. package/template/ts/package.json +0 -32
  62. package/template/ts/src/app.js +0 -75
  63. package/template/ts/src/config/constants.js +0 -20
  64. package/template/ts/src/config/env.js +0 -26
  65. package/template/ts/src/middlewares/errorHandler.js +0 -180
  66. package/template/ts/src/middlewares/requestLogger.js +0 -33
  67. package/template/ts/src/middlewares/validateRequest.js +0 -42
  68. package/template/ts/src/modules/health/controller.js +0 -50
  69. package/template/ts/src/routes.js +0 -17
  70. package/template/ts/src/routes.ts +0 -16
  71. package/template/ts/src/server.js +0 -38
  72. package/template/ts/src/utils/AppError.js +0 -182
  73. package/template/ts/src/utils/logger.js +0 -73
  74. package/template/ts/src/utils/response.js +0 -51
  75. package/template/ts/test-api.js +0 -100
package/bin/index.js CHANGED
@@ -1,26 +1,104 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const path = require("path");
4
4
  const fs = require("fs");
5
- const { execSync } = require("child_process");
6
5
  const prompts = require("prompts");
7
- const { copyTemplateModules } = require("./lib/templateHandler");
6
+
8
7
  const {
9
8
  detectPackageManager,
10
9
  installDependencies,
11
10
  } = require("./lib/pkgManager");
12
11
 
12
+ /**
13
+ * Merge base package.json with a feature package.json
14
+ */
15
+ function mergePackageJson(base, fragment) {
16
+ const merged = { ...base };
17
+
18
+ // Merge dependencies
19
+ if (fragment.dependencies) {
20
+ merged.dependencies = {
21
+ ...merged.dependencies,
22
+ ...fragment.dependencies,
23
+ };
24
+ }
25
+
26
+ // Merge devDependencies
27
+ if (fragment.devDependencies) {
28
+ merged.devDependencies = {
29
+ ...merged.devDependencies,
30
+ ...fragment.devDependencies,
31
+ };
32
+ }
33
+
34
+ // Merge scripts
35
+ if (fragment.scripts) {
36
+ merged.scripts = {
37
+ ...merged.scripts,
38
+ ...fragment.scripts,
39
+ };
40
+ }
41
+
42
+ return merged;
43
+ }
44
+
45
+ function copyDirRecursive(src, dest, excludeFiles = []) {
46
+ if (!fs.existsSync(src)) return;
47
+
48
+ if (!fs.existsSync(dest)) {
49
+ fs.mkdirSync(dest, { recursive: true });
50
+ }
51
+
52
+ const entries = fs.readdirSync(src, { withFileTypes: true });
53
+
54
+ for (const entry of entries) {
55
+ const srcPath = path.join(src, entry.name);
56
+ const destPath = path.join(dest, entry.name);
57
+
58
+ if (excludeFiles.includes(entry.name)) {
59
+ console.log(`Skipping excluded file: ${entry.name}`);
60
+ continue;
61
+ }
62
+
63
+ if (entry.isDirectory()) {
64
+ copyDirRecursive(srcPath, destPath, excludeFiles);
65
+ } else {
66
+ fs.copyFileSync(srcPath, destPath);
67
+ }
68
+ }
69
+ }
70
+
13
71
  (async function main() {
14
72
  try {
15
- console.log("🔥 Welcome to Charcole v2 CLI");
73
+ console.log("🔥 Welcome to Charcole v2.1 CLI");
16
74
 
17
- const responses = await prompts([
18
- {
75
+ // Check if project name is provided as command line argument
76
+ const args = process.argv.slice(2);
77
+ let projectNameFromArgs = null;
78
+
79
+ if (args.length > 0) {
80
+ // The first argument that doesn't start with '-' is likely the project name
81
+ for (const arg of args) {
82
+ if (!arg.startsWith("-")) {
83
+ projectNameFromArgs = arg;
84
+ break;
85
+ }
86
+ }
87
+ }
88
+
89
+ const questions = [];
90
+
91
+ // Only ask for project name if not provided in command line
92
+ if (!projectNameFromArgs) {
93
+ questions.push({
19
94
  type: "text",
20
95
  name: "projectName",
21
96
  message: "Project name:",
22
97
  validate: (name) => (name ? true : "Project name is required"),
23
- },
98
+ });
99
+ }
100
+
101
+ questions.push(
24
102
  {
25
103
  type: "select",
26
104
  name: "language",
@@ -29,25 +107,26 @@ const {
29
107
  { title: "TypeScript", value: "ts" },
30
108
  { title: "JavaScript", value: "js" },
31
109
  ],
32
- initial: 0,
33
110
  },
34
- // TODO: Uncomment when features are implemented
35
- // {
36
- // type: "multiselect",
37
- // name: "features",
38
- // message: "Select features to include:",
39
- // choices: [
40
- // { title: "JWT Authentication", value: "auth" },
41
- // { title: "Swagger Docs", value: "swagger", selected: true },
42
- // { title: "Docker Support", value: "docker" },
43
- // { title: "ESLint + Prettier", value: "lint", selected: true },
44
- // ],
45
- // min: 0,
46
- // },
47
- ]);
48
-
49
- const { projectName, language } = responses;
50
- const features = []; // Empty for now, will be responses.features later
111
+ {
112
+ type: "confirm",
113
+ name: "auth",
114
+ message: "Include JWT authentication module?",
115
+ initial: true,
116
+ },
117
+ );
118
+
119
+ const responses = await prompts(questions);
120
+
121
+ // Use command line project name if provided, otherwise use prompt response
122
+ const projectName = projectNameFromArgs || responses.projectName;
123
+ const { language, auth } = responses;
124
+
125
+ if (!projectName || projectName.trim() === "") {
126
+ console.error("❌ Project name is required");
127
+ process.exit(1);
128
+ }
129
+
51
130
  const targetDir = path.join(process.cwd(), projectName);
52
131
 
53
132
  if (fs.existsSync(targetDir)) {
@@ -56,41 +135,140 @@ const {
56
135
  }
57
136
 
58
137
  const pkgManager = detectPackageManager();
138
+ const templateDir = path.join(__dirname, "..", "template", language);
59
139
 
60
- console.log(`\n📁 Creating project in ${language.toUpperCase()}...`);
140
+ console.log(
141
+ `\n📁 Creating project "${projectName}" in ${language.toUpperCase()}...`,
142
+ );
61
143
 
62
- // Template directory is template/js or template/ts
63
- const templateDir = path.join(__dirname, "..", "template", language);
144
+ fs.mkdirSync(targetDir, { recursive: true });
145
+
146
+ const basePkgPath = path.join(templateDir, "basePackage.json");
147
+ if (!fs.existsSync(basePkgPath)) {
148
+ throw new Error(`basePackage.json not found at ${basePkgPath}`);
149
+ }
64
150
 
65
- copyTemplateModules(templateDir, targetDir, features);
151
+ let mergedPkg = JSON.parse(fs.readFileSync(basePkgPath, "utf-8"));
152
+ console.log("✓ Loaded base package configuration");
66
153
 
67
- // basePackage.json is directly in template/js or template/ts
68
- const basePkg = JSON.parse(
69
- fs.readFileSync(path.join(templateDir, "basePackage.json")),
70
- );
71
- let mergedPkg = { ...basePkg };
72
-
73
- // TODO: Uncomment when features are implemented
74
- // features.forEach((f) => {
75
- // const fragPath = path.join(templateDir, "modules", f, "package.json");
76
- // if (fs.existsSync(fragPath)) {
77
- // const frag = JSON.parse(fs.readFileSync(fragPath));
78
- // mergedPkg.dependencies = {
79
- // ...mergedPkg.dependencies,
80
- // ...frag.dependencies,
81
- // };
82
- // mergedPkg.devDependencies = {
83
- // ...mergedPkg.devDependencies,
84
- // ...frag.devDependencies,
85
- // };
86
- // mergedPkg.scripts = { ...mergedPkg.scripts, ...frag.scripts };
87
- // }
88
- // });
154
+ console.log("\n📁 Copying base template structure...");
155
+
156
+ copyDirRecursive(templateDir, targetDir, ["basePackage.json"]);
157
+
158
+ const templateModulesDir = path.join(templateDir, "src", "modules");
159
+ const targetModulesDir = path.join(targetDir, "src", "modules");
160
+
161
+ if (fs.existsSync(templateModulesDir)) {
162
+ if (!fs.existsSync(targetModulesDir)) {
163
+ fs.mkdirSync(targetModulesDir, { recursive: true });
164
+ }
165
+
166
+ const moduleEntries = fs.readdirSync(templateModulesDir, {
167
+ withFileTypes: true,
168
+ });
169
+
170
+ for (const entry of moduleEntries) {
171
+ if (entry.isDirectory()) {
172
+ const moduleName = entry.name;
173
+ const moduleSrcPath = path.join(templateModulesDir, moduleName);
174
+
175
+ if (moduleName === "auth") {
176
+ if (!auth) {
177
+ console.log(`⏭️ Skipping auth module (not selected)`);
178
+ continue;
179
+ }
180
+ } else {
181
+ const moduleDestPath = path.join(targetModulesDir, moduleName);
182
+ console.log(`📦 Copying ${moduleName} module...`);
183
+ copyDirRecursive(moduleSrcPath, moduleDestPath);
184
+ }
185
+ }
186
+ }
187
+ }
188
+
189
+ // Handle JWT authentication module if selected
190
+ if (auth) {
191
+ console.log("\n📦 Adding JWT authentication module...");
192
+
193
+ // The auth module is in src/modules/auth in the template
194
+ const authModulePath = path.join(templateDir, "src", "modules", "auth");
195
+
196
+ if (!fs.existsSync(authModulePath)) {
197
+ console.error(`❌ Auth module not found at ${authModulePath}`);
198
+ } else {
199
+ // 1. Merge auth module's package.json
200
+ const authPkgPath = path.join(authModulePath, "package.json");
201
+
202
+ if (fs.existsSync(authPkgPath)) {
203
+ try {
204
+ const authPkg = JSON.parse(fs.readFileSync(authPkgPath, "utf-8"));
205
+ console.log("✓ Found auth module package.json");
206
+
207
+ mergedPkg = mergePackageJson(mergedPkg, authPkg);
208
+ console.log("✓ Merged auth module dependencies");
209
+ console.log(
210
+ " Added dependencies:",
211
+ Object.keys(authPkg.dependencies || {}).join(", "),
212
+ );
213
+ if (authPkg.devDependencies) {
214
+ console.log(
215
+ " Added devDependencies:",
216
+ Object.keys(authPkg.devDependencies).join(", "),
217
+ );
218
+ }
219
+ } catch (error) {
220
+ console.error(
221
+ `❌ Failed to parse auth module package.json:`,
222
+ error.message,
223
+ );
224
+ }
225
+ } else {
226
+ console.error(
227
+ "❌ Auth module package.json not found at:",
228
+ authPkgPath,
229
+ );
230
+ }
231
+
232
+ const targetAuthPath = path.join(targetModulesDir, "auth");
233
+ console.log(
234
+ `Copying auth module to: ${targetAuthPath} (excluding package.json)`,
235
+ );
236
+
237
+ copyDirRecursive(authModulePath, targetAuthPath, ["package.json"]);
238
+ console.log("✓ Copied auth module files (package.json was excluded)");
239
+
240
+ const copiedPkgPath = path.join(targetAuthPath, "package.json");
241
+ if (fs.existsSync(copiedPkgPath)) {
242
+ console.warn(
243
+ "⚠️ package.json was accidentally copied, removing it...",
244
+ );
245
+ fs.unlinkSync(copiedPkgPath);
246
+ }
247
+ }
248
+ } else {
249
+ console.log("\n⏭️ Skipping JWT authentication module");
250
+
251
+ const targetAuthPath = path.join(targetDir, "src", "modules", "auth");
252
+ if (fs.existsSync(targetAuthPath)) {
253
+ console.log("Cleaning up auth directory (not selected)...");
254
+ fs.rmSync(targetAuthPath, { recursive: true, force: true });
255
+ }
256
+ }
89
257
 
90
258
  mergedPkg.name = projectName;
91
- fs.writeFileSync(
92
- path.join(targetDir, "package.json"),
93
- JSON.stringify(mergedPkg, null, 2),
259
+
260
+ const finalPkgPath = path.join(targetDir, "package.json");
261
+ fs.writeFileSync(finalPkgPath, JSON.stringify(mergedPkg, null, 2));
262
+ console.log(`\n📝 Created package.json at ${finalPkgPath}`);
263
+
264
+ console.log("\n📦 Final package.json dependencies:");
265
+ console.log(
266
+ " dependencies:",
267
+ Object.keys(mergedPkg.dependencies || {}).join(", "),
268
+ );
269
+ console.log(
270
+ " devDependencies:",
271
+ Object.keys(mergedPkg.devDependencies || {}).join(", "),
94
272
  );
95
273
 
96
274
  console.log(`\n📦 Installing dependencies using ${pkgManager}...`);
@@ -98,10 +276,13 @@ const {
98
276
 
99
277
  console.log("\n✅ Charcole project created successfully!");
100
278
  console.log(
101
- `\n🚀 Next steps:\n cd ${projectName}\n ${pkgManager === "npm" ? "npm run dev" : `${pkgManager} dev`}`,
279
+ `\n🚀 Next steps:\n cd ${projectName}\n ${
280
+ pkgManager === "npm" ? "npm run dev" : `${pkgManager} run dev`
281
+ }`,
102
282
  );
103
283
  } catch (err) {
104
284
  console.error("❌ Failed to create Charcole project:", err.message);
285
+ console.error(err.stack);
105
286
  process.exit(1);
106
287
  }
107
288
  })();
@@ -5,7 +5,6 @@ const path = require("path");
5
5
  /**
6
6
  * Detect which package manager the user is using
7
7
  * Priority: pnpm > yarn > npm
8
- * @returns {string} - Package manager name ('pnpm', 'yarn', or 'npm')
9
8
  */
10
9
  function detectPackageManager() {
11
10
  const userAgent = process.env.npm_config_user_agent;
@@ -28,36 +27,20 @@ function detectPackageManager() {
28
27
  }
29
28
  }
30
29
 
31
- const installedManagers = ["pnpm", "yarn", "npm"].filter((manager) => {
32
- try {
33
- execSync(`${manager} --version`, { stdio: "ignore" });
34
- return true;
35
- } catch {
36
- return false;
37
- }
38
- });
39
-
40
- return installedManagers[0] || "npm";
30
+ return "npm";
41
31
  }
42
32
 
43
33
  /**
44
- * Install dependencies using the detected package manager
45
- * @param {string} targetDir - Project directory where package.json exists
46
- * @param {string} pkgManager - Package manager to use ('pnpm', 'yarn', or 'npm')
34
+ * Install dependencies
47
35
  */
48
36
  function installDependencies(targetDir, pkgManager) {
49
- try {
50
- const installCmd =
51
- pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
37
+ const installCmd =
38
+ pkgManager === "yarn" ? "yarn install" : `${pkgManager} install`;
52
39
 
53
- execSync(installCmd, {
54
- cwd: targetDir,
55
- stdio: "inherit",
56
- });
57
- } catch (error) {
58
- console.error(`\n❌ Failed to install dependencies with ${pkgManager}`);
59
- throw error;
60
- }
40
+ execSync(installCmd, {
41
+ cwd: targetDir,
42
+ stdio: "inherit",
43
+ });
61
44
  }
62
45
 
63
46
  module.exports = {
@@ -2,11 +2,9 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
 
4
4
  /**
5
- * Recursively copy directory contents
6
- * @param {string} src - Source directory
7
- * @param {string} dest - Destination directory
5
+ * Recursively copy directory contents, excluding specific files
8
6
  */
9
- function copyDir(src, dest) {
7
+ function copyDir(src, dest, excludeFiles = []) {
10
8
  if (!fs.existsSync(dest)) {
11
9
  fs.mkdirSync(dest, { recursive: true });
12
10
  }
@@ -17,54 +15,19 @@ function copyDir(src, dest) {
17
15
  const srcPath = path.join(src, entry.name);
18
16
  const destPath = path.join(dest, entry.name);
19
17
 
20
- if (entry.isDirectory()) {
21
- copyDir(srcPath, destPath);
22
- } else {
23
- fs.copyFileSync(srcPath, destPath);
24
- }
25
- }
26
- }
27
-
28
- /**
29
- * Copy template files based on selected language and features
30
- * @param {string} templateDir - Path to template directory (e.g., template/js or template/ts)
31
- * @param {string} targetDir - Destination project directory
32
- * @param {string[]} features - Array of selected features
33
- */
34
- function copyTemplateModules(templateDir, targetDir, features) {
35
- if (!fs.existsSync(targetDir)) {
36
- fs.mkdirSync(targetDir, { recursive: true });
37
- }
38
-
39
- const entries = fs.readdirSync(templateDir, { withFileTypes: true });
40
-
41
- for (const entry of entries) {
42
- const srcPath = path.join(templateDir, entry.name);
43
- const destPath = path.join(targetDir, entry.name);
44
-
45
- if (entry.name === "modules" || entry.name.includes("package.json")) {
18
+ // Skip excluded files
19
+ if (excludeFiles.includes(entry.name)) {
46
20
  continue;
47
21
  }
48
22
 
49
23
  if (entry.isDirectory()) {
50
- copyDir(srcPath, destPath);
24
+ copyDir(srcPath, destPath, excludeFiles);
51
25
  } else {
52
26
  fs.copyFileSync(srcPath, destPath);
53
27
  }
54
28
  }
55
-
56
- const modulesDir = path.join(templateDir, "modules");
57
- if (fs.existsSync(modulesDir)) {
58
- features.forEach((feature) => {
59
- const featurePath = path.join(modulesDir, feature);
60
- if (fs.existsSync(featurePath)) {
61
- copyDir(featurePath, targetDir);
62
- }
63
- });
64
- }
65
29
  }
66
30
 
67
31
  module.exports = {
68
- copyTemplateModules,
69
32
  copyDir,
70
33
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-charcole",
3
- "version": "2.0.4",
4
- "description": "Production-ready Express backend starter with engineering guardrails.",
3
+ "version": "2.1.0",
4
+ "description": "CLI to create production-ready Node.js Express APIs with TypeScript/JavaScript, JWT auth, and repository pattern",
5
5
  "license": "MIT",
6
6
  "author": {
7
7
  "name": "Sheraz Manzoor",
@@ -0,0 +1,20 @@
1
+ # Charcole v2.1 Plan
2
+
3
+ ## Features
4
+
5
+ - [DONE] Auth module (JWT)
6
+ - [DONE] CLI feature flag for auth
7
+ - [DONE] JS template support
8
+ - [DONE] TS template support
9
+
10
+ ## Non-Goals
11
+
12
+ - No OAuth
13
+ - No DB choice
14
+ - No breaking changes
15
+
16
+ ## Release
17
+
18
+ - [ ] Changelog
19
+ - [ ] Docs update
20
+ - [ ] npm publish
@@ -1,8 +1,16 @@
1
+ # Server Configuration
1
2
  NODE_ENV=development
2
3
  PORT=3000
3
4
 
5
+ # Logging
4
6
  LOG_LEVEL=info
5
7
 
8
+ # CORS
6
9
  CORS_ORIGIN=*
7
10
 
11
+ # Request
8
12
  REQUEST_TIMEOUT=30000
13
+
14
+
15
+ # Authentication
16
+ JWT_SECRET=your-secret-key-here
@@ -1,28 +1,26 @@
1
1
  {
2
2
  "name": "charcole",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "Production-grade Node.js Express API",
5
5
  "main": "src/server.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=18.0.0"
9
+ },
6
10
  "scripts": {
7
11
  "start": "node src/server.js",
8
12
  "dev": "nodemon src/server.js",
9
- "lint": "echo 'Add linting here'",
10
- "test": "echo 'Add tests here'"
13
+ "lint": "echo \"Add linting here\"",
14
+ "test": "echo \"Add tests here\""
11
15
  },
12
- "engines": {
13
- "node": ">=18.0.0"
14
- },
15
- "keywords": [],
16
- "author": "",
17
- "license": "ISC",
18
- "type": "module",
19
16
  "dependencies": {
17
+ "express": "^4.18.2",
20
18
  "cors": "^2.8.5",
21
19
  "dotenv": "^16.3.1",
22
- "express": "^4.18.2",
23
- "zod": "^3.22.4"
20
+ "zod": "^3.25.76"
24
21
  },
25
22
  "devDependencies": {
26
23
  "nodemon": "^3.0.2"
27
- }
24
+ },
25
+ "license": "ISC"
28
26
  }
@@ -1,4 +1,5 @@
1
1
  import express from "express";
2
+ import { userRepo } from "./repositories/user.repo.js";
2
3
  import cors from "cors";
3
4
  import { env } from "./config/env.js";
4
5
  import { HTTP_STATUS, ERROR_MESSAGES } from "./config/constants.js";
@@ -10,7 +11,7 @@ import {
10
11
  } from "./middlewares/errorHandler.js";
11
12
  import { sendSuccess } from "./utils/response.js";
12
13
  import { logger } from "./utils/logger.js";
13
- import routes from "./routes.js";
14
+ import routes from "./routes/index.js";
14
15
 
15
16
  export const app = express();
16
17
 
@@ -73,3 +74,5 @@ app.use((req, res, next) => {
73
74
  app.use(errorHandler);
74
75
 
75
76
  logger.info("Express app configured successfully");
77
+
78
+ app.locals.userRepo = userRepo;
@@ -0,0 +1,3 @@
1
+ export const USER_ROLES = ["user", "admin"];
2
+
3
+ export const AUTH_PROVIDERS = ["credentials"];
@@ -0,0 +1,29 @@
1
+ import { AuthService } from "./auth.service.js";
2
+
3
+ export const AuthController = {
4
+ async register(req, res) {
5
+ try {
6
+ const user = await AuthService.register(
7
+ req.body,
8
+ req.app.locals.userRepo,
9
+ );
10
+
11
+ res.status(201).json({
12
+ message: "User registered successfully",
13
+ user,
14
+ });
15
+ } catch (err) {
16
+ res.status(400).json({ message: err.message });
17
+ }
18
+ },
19
+
20
+ async login(req, res) {
21
+ try {
22
+ const result = await AuthService.login(req.body, req.app.locals.userRepo);
23
+
24
+ res.json(result);
25
+ } catch (err) {
26
+ res.status(401).json({ message: err.message });
27
+ }
28
+ },
29
+ };
@@ -0,0 +1,19 @@
1
+ import jwt from "jsonwebtoken";
2
+
3
+ export const requireAuth = (req, res, next) => {
4
+ const authHeader = req.headers.authorization;
5
+
6
+ if (!authHeader?.startsWith("Bearer ")) {
7
+ return res.status(401).json({ message: "Unauthorized" });
8
+ }
9
+
10
+ const token = authHeader.split(" ")[1];
11
+
12
+ try {
13
+ const payload = jwt.verify(token, process.env.JWT_SECRET);
14
+ req.user = payload;
15
+ next();
16
+ } catch {
17
+ res.status(401).json({ message: "Invalid token" });
18
+ }
19
+ };
@@ -0,0 +1,9 @@
1
+ import { Router } from "express";
2
+ import { AuthController } from "./auth.controller.js";
3
+
4
+ const router = Router();
5
+
6
+ router.post("/register", AuthController.register);
7
+ router.post("/login", AuthController.login);
8
+
9
+ export default router;