deploy-bbc 1.0.0 → 1.2.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 (44) hide show
  1. package/CLAUDE.md +85 -11
  2. package/cli/package.json +9 -9
  3. package/cli/src/cli/index.ts +8 -3
  4. package/cli/src/helpers/log-next-steps.ts +22 -19
  5. package/cli/src/helpers/scaffold-project.ts +30 -4
  6. package/cli/src/templates/base/package.json +2 -1
  7. package/cli/src/templates/base/src/controllers/user/create-user.controller.ts +84 -0
  8. package/cli/src/templates/base/src/controllers/user/delete-user.controller.ts +60 -0
  9. package/cli/src/templates/base/src/controllers/user/get-user.controller.ts +74 -0
  10. package/cli/src/templates/base/src/controllers/user/get-users.controller.ts +41 -0
  11. package/cli/src/templates/base/src/controllers/user/update-user.controller.ts +111 -0
  12. package/cli/src/templates/base/src/models/user.model.ts +83 -0
  13. package/cli/src/templates/base/src/routes/index.ts +5 -0
  14. package/cli/src/templates/base/src/routes/user.route.ts +17 -0
  15. package/cli/src/templates/base/src/types/common/api-response.types.ts +30 -0
  16. package/cli/src/templates/base/src/types/index.ts +5 -2
  17. package/cli/src/templates/base/src/types/models/user.types.ts +26 -0
  18. package/cli/src/templates/base-bun-native/package.json +2 -1
  19. package/cli/src/templates/base-bun-native/src/controllers/user/create-user.controller.ts +76 -0
  20. package/cli/src/templates/base-bun-native/src/controllers/user/delete-user.controller.ts +53 -0
  21. package/cli/src/templates/base-bun-native/src/controllers/user/get-user.controller.ts +67 -0
  22. package/cli/src/templates/base-bun-native/src/controllers/user/get-users.controller.ts +40 -0
  23. package/cli/src/templates/base-bun-native/src/controllers/user/update-user.controller.ts +101 -0
  24. package/cli/src/templates/base-bun-native/src/index.ts +3 -3
  25. package/cli/src/templates/base-bun-native/src/models/user.model.ts +83 -0
  26. package/cli/src/templates/base-bun-native/src/routes/index.ts +10 -6
  27. package/cli/src/templates/base-bun-native/src/routes/user.route.ts +50 -0
  28. package/cli/src/templates/base-bun-native/src/types/common/api-response.types.ts +30 -0
  29. package/cli/src/templates/base-bun-native/src/types/index.ts +5 -2
  30. package/cli/src/templates/base-bun-native/src/types/models/user.types.ts +26 -0
  31. package/cli/src/templates/base-express/package.json +2 -1
  32. package/cli/src/templates/base-express/src/controllers/user/create-user.controller.ts +75 -0
  33. package/cli/src/templates/base-express/src/controllers/user/delete-user.controller.ts +56 -0
  34. package/cli/src/templates/base-express/src/controllers/user/get-user.controller.ts +70 -0
  35. package/cli/src/templates/base-express/src/controllers/user/get-users.controller.ts +41 -0
  36. package/cli/src/templates/base-express/src/controllers/user/update-user.controller.ts +102 -0
  37. package/cli/src/templates/base-express/src/models/user.model.ts +83 -0
  38. package/cli/src/templates/base-express/src/routes/index.ts +5 -0
  39. package/cli/src/templates/base-express/src/routes/user.route.ts +17 -0
  40. package/cli/src/templates/base-express/src/types/common/api-response.types.ts +30 -0
  41. package/cli/src/templates/base-express/src/types/index.ts +5 -2
  42. package/cli/src/templates/base-express/src/types/models/user.types.ts +26 -0
  43. package/cli/src/utils/parse-name-and-path.ts +18 -2
  44. package/package.json +3 -3
package/CLAUDE.md CHANGED
@@ -105,20 +105,86 @@ This document outlines the coding conventions and guidelines for the `create-bac
105
105
 
106
106
  ---
107
107
 
108
- ## 📦 Type & Interface Naming
108
+ ## 📦 Type Naming & Usage
109
109
 
110
- ### Use `PascalCase` for types, interfaces, enums, and classes
110
+ ### Use `PascalCase` for types, enums, and classes
111
111
 
112
112
  ```typescript
113
- interface CliResults { }
113
+ type CliResults = { }
114
114
  ✅ type InstallerOptions = { }
115
115
  ✅ enum AvailablePackages { }
116
116
  ✅ class ProjectScaffold { }
117
117
 
118
- interface cli_results { }
118
+ type cli_results = { }
119
119
  ❌ type installer_options = { }
120
120
  ```
121
121
 
122
+ ### ⚠️ ALWAYS use `type` instead of `interface`
123
+
124
+ ```typescript
125
+ ✅ type CliResults = {
126
+ appName: string;
127
+ flags: {
128
+ noGit: boolean;
129
+ noInstall: boolean;
130
+ };
131
+ packages: string[];
132
+ }
133
+
134
+ ✅ type ScaffoldOptions = {
135
+ projectDir: string;
136
+ appName: string;
137
+ packages: string[];
138
+ }
139
+
140
+ ❌ interface CliResults {
141
+ appName: string;
142
+ flags: {
143
+ noGit: boolean;
144
+ noInstall: boolean;
145
+ };
146
+ packages: string[];
147
+ }
148
+
149
+ ❌ interface ScaffoldOptions {
150
+ projectDir: string;
151
+ appName: string;
152
+ packages: string[];
153
+ }
154
+ ```
155
+
156
+ **Why `type` over `interface`?**
157
+
158
+ - **Consistency**: Single way to define object shapes
159
+ - **Flexibility**: Types support unions, intersections, and mapped types more naturally
160
+ - **Composability**: Better for complex type operations and transformations
161
+ - **Simplicity**: One less concept to remember
162
+ - **Modern practice**: Aligns with contemporary TypeScript patterns
163
+
164
+ **Extending types:**
165
+
166
+ ```typescript
167
+ ✅ type BaseOptions = {
168
+ projectDir: string;
169
+ appName: string;
170
+ }
171
+
172
+ ✅ type ExtendedOptions = BaseOptions & {
173
+ packages: string[];
174
+ flags: Record<string, boolean>;
175
+ }
176
+
177
+ ❌ interface BaseOptions {
178
+ projectDir: string;
179
+ appName: string;
180
+ }
181
+
182
+ ❌ interface ExtendedOptions extends BaseOptions {
183
+ packages: string[];
184
+ flags: Record<string, boolean>;
185
+ }
186
+ ```
187
+
122
188
  ---
123
189
 
124
190
  ## 🗂️ Import/Export Conventions
@@ -163,7 +229,7 @@ import { install_dependencies } from "./install-dependencies.js";
163
229
  const DEFAULT_PROJECT_DIR = process.cwd();
164
230
  const TEMPLATE_BASE_PATH = "../templates/base";
165
231
 
166
- interface ScaffoldOptions {
232
+ type ScaffoldOptions = {
167
233
  projectDir: string;
168
234
  appName: string;
169
235
  packages: string[];
@@ -247,7 +313,9 @@ To enforce these conventions, configure ESLint:
247
313
  "selector": "typeLike",
248
314
  "format": ["PascalCase"]
249
315
  }
250
- ]
316
+ ],
317
+ "@typescript-eslint/consistent-type-definitions": ["error", "type"],
318
+ "@typescript-eslint/no-empty-interface": "error"
251
319
  }
252
320
  }
253
321
  ```
@@ -262,6 +330,7 @@ To enforce these conventions, configure ESLint:
262
330
  ❌ function createProject() { } // camelCase function
263
331
  ❌ const project_dir = ""; // snake_case variable
264
332
  ❌ src/CreateProject.ts // PascalCase file
333
+ ❌ interface CliResults { } // Using interface
265
334
  ```
266
335
 
267
336
  ### ✅ Do stick to the rules
@@ -270,6 +339,7 @@ To enforce these conventions, configure ESLint:
270
339
  ✅ function create_project() { } // snake_case function
271
340
  ✅ const projectDir = ""; // camelCase variable
272
341
  ✅ src/create-project.ts // kebab-case file
342
+ ✅ type CliResults = { } // Using type
273
343
  ```
274
344
 
275
345
  ---
@@ -283,9 +353,10 @@ To enforce these conventions, configure ESLint:
283
353
  | **Functions** | `snake_case` | `function create_project()` |
284
354
  | **Variables** | `camelCase` | `const projectDir` |
285
355
  | **Constants** | `SCREAMING_SNAKE_CASE` | `const MAX_RETRIES` |
286
- | **Types/Interfaces** | `PascalCase` | `interface CliResults` |
356
+ | **Types** | `PascalCase` + `type` keyword | `type CliResults = { }` |
287
357
  | **Enums** | `PascalCase` | `enum AvailablePackages` |
288
358
  | **Classes** | `PascalCase` | `class ProjectScaffold` |
359
+ | **Object Shapes** | Use `type`, NOT `interface` | `type Config = { }` |
289
360
 
290
361
  ---
291
362
 
@@ -294,10 +365,11 @@ To enforce these conventions, configure ESLint:
294
365
  When contributing to this project, please:
295
366
 
296
367
  1. ✅ Follow ALL conventions outlined in this document
297
- 2. ✅ Run linting before committing: `bun run lint`
298
- 3. ✅ Format code: `bun run format`
299
- 4. ✅ Use descriptive commit messages
300
- 5. ✅ Add JSDoc comments for exported functions
368
+ 2. ✅ Use `type` instead of `interface` for all object type definitions
369
+ 3. ✅ Run linting before committing: `bun run lint`
370
+ 4. ✅ Format code: `bun run format`
371
+ 5. ✅ Use descriptive commit messages
372
+ 6. ✅ Add JSDoc comments for exported functions
301
373
 
302
374
  ---
303
375
 
@@ -310,6 +382,7 @@ These conventions are chosen to:
310
382
  - **Reduce cognitive load** when working with multiple languages
311
383
  - **Follow industry standards** where applicable
312
384
  - **Enable better tooling support**
385
+ - **Simplify type definitions** by using only `type` declarations
313
386
 
314
387
  ---
315
388
 
@@ -318,6 +391,7 @@ These conventions are chosen to:
318
391
  - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
319
392
  - [TypeScript Style Guide](https://google.github.io/styleguide/tsguide.html)
320
393
  - [File Naming Conventions](https://github.com/kettanaito/naming-cheatsheet)
394
+ - [Types vs Interfaces](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces)
321
395
 
322
396
  ---
323
397
 
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-bbc",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI to bootstrap production-ready backends with Bun (Best Backend Code)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -38,17 +38,17 @@
38
38
  "url": "https://github.com/aritra69/deploy-bbc/issues"
39
39
  },
40
40
  "dependencies": {
41
- "@clack/prompts": "^0.7.0",
42
- "chalk": "^5.3.0",
43
- "commander": "^11.1.0",
44
- "execa": "^8.0.1",
45
- "fs-extra": "^11.2.0",
46
- "ora": "^7.0.1"
41
+ "@clack/prompts": "^0.11.0",
42
+ "chalk": "^5.6.2",
43
+ "commander": "^14.0.2",
44
+ "execa": "^9.6.1",
45
+ "fs-extra": "^11.3.3",
46
+ "ora": "^9.1.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/bun": "latest",
50
50
  "@types/fs-extra": "^11.0.4",
51
- "@types/node": "^20.10.6",
52
- "typescript": "^5.3.3"
51
+ "@types/node": "^25.0.10",
52
+ "typescript": "^5.9.3"
53
53
  }
54
54
  }
@@ -100,12 +100,17 @@ export const run_cli = async (): Promise<CliResults> => {
100
100
  projectName: () =>
101
101
  p.text({
102
102
  message: "What will your project be called?",
103
- placeholder: "my-awesome-api",
103
+ placeholder: "my-awesome-api (or . for current directory)",
104
104
  defaultValue: cliProvidedName || "my-backend",
105
105
  validate: (value) => {
106
106
  if (!value) return "Please enter a project name";
107
- if (!/^[a-z0-9-_]+$/i.test(value))
108
- return "Only letters, numbers, dashes and underscores";
107
+ // Allow '.' for current directory or paths
108
+ if (value === "." || value.startsWith("./") || value.startsWith("../") || value.startsWith("~/")) {
109
+ return; // Valid path
110
+ }
111
+ // Otherwise check for valid name format
112
+ if (!/^[a-z0-9-_/]+$/i.test(value))
113
+ return "Only letters, numbers, dashes, underscores, and slashes";
109
114
  },
110
115
  }),
111
116
 
@@ -14,22 +14,31 @@ export function log_next_steps(
14
14
  options: InstallerOptions,
15
15
  _cliResults: CliResults
16
16
  ): void {
17
- const { appName, packages } = options;
17
+ const { appName, packages, projectDir } = options;
18
+
19
+ // Check if project was created in current directory
20
+ const isCurrentDirectory = projectDir === process.cwd();
18
21
 
19
22
  console.log("\n" + chalk.bold.green("✨ Project created successfully!"));
20
23
  console.log("\n" + chalk.bold("Next steps:"));
21
24
  console.log();
22
25
 
23
- // Step 1: Navigate to project
24
- console.log(chalk.cyan("1.") + " Navigate to your project:");
25
- console.log(` ${chalk.gray("cd")} ${appName}`);
26
- console.log();
26
+ let stepNumber = 1;
27
+
28
+ // Step 1: Navigate to project (skip if current directory)
29
+ if (!isCurrentDirectory) {
30
+ console.log(chalk.cyan(`${stepNumber}.`) + " Navigate to your project:");
31
+ console.log(` ${chalk.gray("cd")} ${appName}`);
32
+ console.log();
33
+ stepNumber++;
34
+ }
27
35
 
28
- // Step 2: Environment variables
29
- console.log(chalk.cyan("2.") + " Set up environment variables:");
36
+ // Step: Environment variables
37
+ console.log(chalk.cyan(`${stepNumber}.`) + " Set up environment variables:");
30
38
  console.log(` ${chalk.gray("cp")} .env.example .env`);
31
39
  console.log(` ${chalk.gray("# Edit .env with your configuration")}`);
32
40
  console.log();
41
+ stepNumber++;
33
42
 
34
43
  // Step 3: Docker (if database or redis selected)
35
44
  const hasDocker =
@@ -39,9 +48,10 @@ export function log_next_steps(
39
48
  packages.includes(AvailablePackages.redis);
40
49
 
41
50
  if (hasDocker) {
42
- console.log(chalk.cyan("3.") + " Start Docker services:");
51
+ console.log(chalk.cyan(`${stepNumber}.`) + " Start Docker services:");
43
52
  console.log(` ${chalk.gray("docker-compose up -d")}`);
44
53
  console.log();
54
+ stepNumber++;
45
55
  }
46
56
 
47
57
  // Step 4: Database migrations (if postgres or mysql)
@@ -50,21 +60,14 @@ export function log_next_steps(
50
60
  packages.includes(AvailablePackages.mysql);
51
61
 
52
62
  if (needsMigration) {
53
- const stepNum = hasDocker ? "4" : "3";
54
- console.log(chalk.cyan(`${stepNum}.`) + " Run database migrations:");
63
+ console.log(chalk.cyan(`${stepNumber}.`) + " Run database migrations:");
55
64
  console.log(` ${chalk.gray("bun run db:migrate")}`);
56
65
  console.log();
66
+ stepNumber++;
57
67
  }
58
68
 
59
- // Step 5: Start development server
60
- const lastStepNum = needsMigration
61
- ? hasDocker
62
- ? "5"
63
- : "4"
64
- : hasDocker
65
- ? "4"
66
- : "3";
67
- console.log(chalk.cyan(`${lastStepNum}.`) + " Start the development server:");
69
+ // Step: Start development server
70
+ console.log(chalk.cyan(`${stepNumber}.`) + " Start the development server:");
68
71
  console.log(` ${chalk.gray("bun run dev")}`);
69
72
  console.log();
70
73
 
@@ -9,10 +9,14 @@ const __dirname = path.dirname(__filename);
9
9
 
10
10
  /**
11
11
  * Scaffolds the base project structure by copying template files.
12
- * Creates the project directory and copies all files from templates/base/
12
+ * Creates the project directory and copies all files from the selected template.
13
+ *
14
+ * Special handling:
15
+ * - If projectDir is current directory: allows scaffolding but checks for conflicts (src/, package.json)
16
+ * - If projectDir is a new directory: creates it and requires it to be empty
13
17
  *
14
18
  * @param options - Installer options containing projectDir and other config
15
- * @throws Error if project directory already exists and is not empty
19
+ * @throws Error if project directory already exists and is not empty, or if conflicts detected in current directory
16
20
  */
17
21
  export async function scaffold_project(
18
22
  options: InstallerOptions
@@ -21,8 +25,10 @@ export async function scaffold_project(
21
25
  const spinner = ora("Scaffolding project...").start();
22
26
 
23
27
  try {
24
- // Check if directory exists and is not empty
25
- if (await fs.pathExists(projectDir)) {
28
+ const isCurrentDirectory = projectDir === process.cwd();
29
+
30
+ // Check if directory exists and is not empty (skip for current directory)
31
+ if (!isCurrentDirectory && await fs.pathExists(projectDir)) {
26
32
  const files = await fs.readdir(projectDir);
27
33
  if (files.length > 0) {
28
34
  spinner.fail(`Directory ${projectDir} already exists and is not empty`);
@@ -32,6 +38,26 @@ export async function scaffold_project(
32
38
  }
33
39
  }
34
40
 
41
+ // If scaffolding in current directory, check for conflicts
42
+ if (isCurrentDirectory) {
43
+ const conflictingPaths = ["src", "package.json"];
44
+ const conflicts = [];
45
+
46
+ for (const pathToCheck of conflictingPaths) {
47
+ if (await fs.pathExists(path.join(projectDir, pathToCheck))) {
48
+ conflicts.push(pathToCheck);
49
+ }
50
+ }
51
+
52
+ if (conflicts.length > 0) {
53
+ spinner.fail("Cannot scaffold in current directory");
54
+ throw new Error(
55
+ `The following files/folders already exist: ${conflicts.join(", ")}\n` +
56
+ "Please use an empty directory or choose a different location."
57
+ );
58
+ }
59
+ }
60
+
35
61
  // Ensure project directory exists
36
62
  await fs.ensureDir(projectDir);
37
63
 
@@ -10,8 +10,9 @@
10
10
  "type-check": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
+ "dotenv": "^16.3.1",
13
14
  "hono": "^4.0.0",
14
- "dotenv": "^16.3.1"
15
+ "zod": "^3.22.4"
15
16
  },
16
17
  "devDependencies": {
17
18
  "@types/bun": "latest",
@@ -0,0 +1,84 @@
1
+ import type { Context } from "hono";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+ import type { UserResponse } from "../../types/models/user.types.js";
6
+
7
+ /**
8
+ * Validation schema
9
+ */
10
+ const create_user_schema = z.object({
11
+ email: z.string().email("Invalid email address"),
12
+ name: z.string().min(2, "Name must be at least 2 characters").max(100, "Name must not exceed 100 characters"),
13
+ });
14
+
15
+ /**
16
+ * Helper to format user response
17
+ */
18
+ function format_user_response(user: any): UserResponse {
19
+ return {
20
+ id: user.id,
21
+ email: user.email,
22
+ name: user.name,
23
+ createdAt: user.createdAt.toISOString(),
24
+ updatedAt: user.updatedAt.toISOString(),
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Create a new user
30
+ * POST /users
31
+ */
32
+ export async function create_user(c: Context) {
33
+ try {
34
+ const body = await c.req.json();
35
+
36
+ // Validate request body
37
+ const validation = create_user_schema.safeParse(body);
38
+ if (!validation.success) {
39
+ return c.json<ApiError>(
40
+ {
41
+ success: false,
42
+ error: "Validation failed",
43
+ details: validation.error.errors.map((err) => ({
44
+ field: err.path.join("."),
45
+ message: err.message,
46
+ })),
47
+ },
48
+ 400
49
+ );
50
+ }
51
+
52
+ // Check if email already exists
53
+ const existing_user = await user_model.find_by_email(validation.data.email);
54
+ if (existing_user) {
55
+ return c.json<ApiError>(
56
+ {
57
+ success: false,
58
+ error: "User with this email already exists",
59
+ },
60
+ 409
61
+ );
62
+ }
63
+
64
+ // Create user
65
+ const user = await user_model.create(validation.data);
66
+
67
+ return c.json<ApiResponse<UserResponse>>(
68
+ {
69
+ success: true,
70
+ data: format_user_response(user),
71
+ message: "User created successfully",
72
+ },
73
+ 201
74
+ );
75
+ } catch (error) {
76
+ return c.json<ApiError>(
77
+ {
78
+ success: false,
79
+ error: "Failed to create user",
80
+ },
81
+ 500
82
+ );
83
+ }
84
+ }
@@ -0,0 +1,60 @@
1
+ import type { Context } from "hono";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+
6
+ /**
7
+ * Validation schema
8
+ */
9
+ const user_id_schema = z.string().uuid("Invalid user ID format");
10
+
11
+ /**
12
+ * Delete user by ID
13
+ * DELETE /users/:id
14
+ */
15
+ export async function delete_user(c: Context) {
16
+ try {
17
+ const id = c.req.param("id");
18
+
19
+ // Validate ID format
20
+ const validation = user_id_schema.safeParse(id);
21
+ if (!validation.success) {
22
+ return c.json<ApiError>(
23
+ {
24
+ success: false,
25
+ error: "Invalid user ID",
26
+ details: validation.error.errors.map((err) => ({
27
+ field: err.path.join("."),
28
+ message: err.message,
29
+ })),
30
+ },
31
+ 400
32
+ );
33
+ }
34
+
35
+ const deleted = await user_model.delete(id);
36
+
37
+ if (!deleted) {
38
+ return c.json<ApiError>(
39
+ {
40
+ success: false,
41
+ error: "User not found",
42
+ },
43
+ 404
44
+ );
45
+ }
46
+
47
+ return c.json<ApiResponse>({
48
+ success: true,
49
+ message: "User deleted successfully",
50
+ });
51
+ } catch (error) {
52
+ return c.json<ApiError>(
53
+ {
54
+ success: false,
55
+ error: "Failed to delete user",
56
+ },
57
+ 500
58
+ );
59
+ }
60
+ }
@@ -0,0 +1,74 @@
1
+ import type { Context } from "hono";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+ import type { UserResponse } from "../../types/models/user.types.js";
6
+
7
+ /**
8
+ * Validation schema
9
+ */
10
+ const user_id_schema = z.string().uuid("Invalid user ID format");
11
+
12
+ /**
13
+ * Helper to format user response
14
+ */
15
+ function format_user_response(user: any): UserResponse {
16
+ return {
17
+ id: user.id,
18
+ email: user.email,
19
+ name: user.name,
20
+ createdAt: user.createdAt.toISOString(),
21
+ updatedAt: user.updatedAt.toISOString(),
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Get user by ID
27
+ * GET /users/:id
28
+ */
29
+ export async function get_user(c: Context) {
30
+ try {
31
+ const id = c.req.param("id");
32
+
33
+ // Validate ID format
34
+ const validation = user_id_schema.safeParse(id);
35
+ if (!validation.success) {
36
+ return c.json<ApiError>(
37
+ {
38
+ success: false,
39
+ error: "Invalid user ID",
40
+ details: validation.error.errors.map((err) => ({
41
+ field: err.path.join("."),
42
+ message: err.message,
43
+ })),
44
+ },
45
+ 400
46
+ );
47
+ }
48
+
49
+ const user = await user_model.find_by_id(id);
50
+
51
+ if (!user) {
52
+ return c.json<ApiError>(
53
+ {
54
+ success: false,
55
+ error: "User not found",
56
+ },
57
+ 404
58
+ );
59
+ }
60
+
61
+ return c.json<ApiResponse<UserResponse>>({
62
+ success: true,
63
+ data: format_user_response(user),
64
+ });
65
+ } catch (error) {
66
+ return c.json<ApiError>(
67
+ {
68
+ success: false,
69
+ error: "Failed to fetch user",
70
+ },
71
+ 500
72
+ );
73
+ }
74
+ }
@@ -0,0 +1,41 @@
1
+ import type { Context } from "hono";
2
+ import { user_model } from "../../models/user.model.js";
3
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
4
+ import type { UserResponse } from "../../types/models/user.types.js";
5
+
6
+ /**
7
+ * Helper to format user response
8
+ */
9
+ function format_user_response(user: any): UserResponse {
10
+ return {
11
+ id: user.id,
12
+ email: user.email,
13
+ name: user.name,
14
+ createdAt: user.createdAt.toISOString(),
15
+ updatedAt: user.updatedAt.toISOString(),
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Get all users
21
+ * GET /users
22
+ */
23
+ export async function get_users(c: Context) {
24
+ try {
25
+ const users = await user_model.find_all();
26
+ const formatted_users = users.map(format_user_response);
27
+
28
+ return c.json<ApiResponse<UserResponse[]>>({
29
+ success: true,
30
+ data: formatted_users,
31
+ });
32
+ } catch (error) {
33
+ return c.json<ApiError>(
34
+ {
35
+ success: false,
36
+ error: "Failed to fetch users",
37
+ },
38
+ 500
39
+ );
40
+ }
41
+ }