Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/modules/user/domain/entities/User.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export class User extends BaseEntity {
@Column()
password: string;

@Column({ unique: true })
wallet: string;
@Column({ unique: true, nullable: true })
wallet?: string;

@Column({ default: false })
isVerified: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/modules/user/domain/interfaces/IUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export interface IUser {
lastName: string;
email: string;
password: string;
wallet: string;
wallet?: string;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Wallet made optional: contract change looks good; confirm downstream assumptions

Aligns with the entity change. Please verify no service/mapper still treats wallet as required and that a DB migration exists to make users.wallet nullable in all environments.

Run:


🏁 Script executed:

#!/bin/bash
# Find places that may still require a wallet or assume non-null
rg -nP --type=ts -C2 '\bwallet\s*:\s*string\b|\.wallet\b(?!\?)'

Length of output: 5604


🏁 Script executed:

#!/bin/bash
# List files with 'migration' in name or content
rg -i migration -l || echo "No files with 'migration' in name/content"
# Check for altering users.wallet nullability in those files
rg -nP 'wallet.*nullable|nullable.*wallet|alter.*wallet' -g '*migration*' -C2 || echo "No wallet nullability changes found in migration files"

Length of output: 1013


Add missing DB migration and update code to handle optional wallet

  • No migration found modifying users.wallet to nullable; add a migration (e.g. via Prisma) to alter the column in all environments.
  • Update downstream code still assuming non-null wallets:
    • src/modules/user/use-cases/userUseCase.ts: user.wallet = data.wallet
    • src/modules/auth/dto/emailVerification.dto.ts: wallet: string and default ""
    • src/repository/IUserRepository.ts & src/repository/user.repository.ts: wallet parameter still required
    • Integration and entity tests in __tests__ still expect wallet to always be present
🤖 Prompt for AI Agents
In src/modules/user/domain/interfaces/IUser.ts around line 7, users.wallet was
made optional but there is no DB migration and multiple callers still assume a
non-null wallet; create and apply a DB migration (e.g. Prisma migration) to
alter users.wallet to nullable in all environments, then update code to accept
and propagate an optional wallet: change assignments like user.wallet =
data.wallet to handle undefined/null (e.g. only assign when present or allow
null), change DTOs (src/modules/auth/dto/emailVerification.dto.ts) to make
wallet optional (remove default "" and type string -> string | undefined),
update repository interface and implementation
(src/repository/IUserRepository.ts & src/repository/user.repository.ts) to
accept an optional wallet parameter, and adjust integration/entity tests in
__tests__ to account for nullable wallets (add scenarios for missing wallet and
update expectations); run migrations, update seeds and regenerate client if
applicable.

isVerified: boolean;
verificationToken?: string;
verificationTokenExpires?: Date;
Expand Down
4 changes: 4 additions & 0 deletions src/modules/user/dto/UpdateUserDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
} from "class-validator";

export class UpdateUserDto {
@IsOptional()
@IsString({ message: "ID must be a string" })
id?: string;

@IsOptional()
@IsString({ message: "Name must be a string" })
@MinLength(2, { message: "Name must be at least 2 characters long" })
Expand Down
80 changes: 0 additions & 80 deletions src/modules/user/presentation/controllers/UserController.disabled

This file was deleted.

43 changes: 0 additions & 43 deletions src/modules/user/presentation/controllers/UserController.stub.ts

This file was deleted.

105 changes: 105 additions & 0 deletions src/modules/user/presentation/controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Request, Response } from "express";
import { CreateUserUseCase } from "../../use-cases/userUseCase";
import { GetUserByIdUseCase } from "../../use-cases/userUseCase";
import { GetUserByEmailUseCase } from "../../use-cases/userUseCase";
import { UpdateUserUseCase } from "../../use-cases/userUseCase";
import { DeleteUserUseCase } from "../../use-cases/userUseCase";
import { PrismaUserRepository } from "../../repositories/PrismaUserRepository";
import { CreateUserDto } from "../../dto/CreateUserDto";
import { UpdateUserDto } from "../../dto/UpdateUserDto";

export default class UserController {
private userRepository: PrismaUserRepository;
private createUserUseCase: CreateUserUseCase;
private getUserByIdUseCase: GetUserByIdUseCase;
private getUserByEmailUseCase: GetUserByEmailUseCase;
private updateUserUseCase: UpdateUserUseCase;
private deleteUserUseCase: DeleteUserUseCase;

constructor() {
this.userRepository = new PrismaUserRepository();
this.createUserUseCase = new CreateUserUseCase(this.userRepository);
this.getUserByIdUseCase = new GetUserByIdUseCase(this.userRepository);
this.getUserByEmailUseCase = new GetUserByEmailUseCase(this.userRepository);
this.updateUserUseCase = new UpdateUserUseCase(this.userRepository);
this.deleteUserUseCase = new DeleteUserUseCase(this.userRepository);
}

async createUser(req: Request, res: Response): Promise<void> {
try {
const userDto = new CreateUserDto();
Object.assign(userDto, req.body);

const user = await this.createUserUseCase.execute(userDto);
res.status(201).json(user);
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Comment on lines +28 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Prevent leaking sensitive fields in create response; add DTO validation and consistent error mapping.

Currently, createUser returns the raw user object, which likely includes password (hashed) and possibly verification token fields. Also, DTO decorators aren’t enforced without explicit validation, and error mapping is inconsistent with the intended 404/409 semantics.

Apply this diff in createUser:

   async createUser(req: Request, res: Response): Promise<void> {
     try {
-      const userDto = new CreateUserDto();
-      Object.assign(userDto, req.body);
-
-      const user = await this.createUserUseCase.execute(userDto);
-      res.status(201).json(user);
+      const userDto = Object.assign(new CreateUserDto(), req.body);
+      if (!(await this.validateOr400(userDto, res))) return;
+      const user = await this.createUserUseCase.execute(userDto);
+      res.status(201).json(this.toSafeUser(user));
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }

Add the helper methods shown in a later comment to support toSafeUser, validateOr400, and handleError.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/user/presentation/controllers/UserController.ts around lines 28
to 40, the createUser method currently returns raw user objects (potentially
leaking sensitive fields like password and verification tokens), lacks DTO
validation enforcement, and maps errors inconsistently; fix it by 1) validating
the incoming CreateUserDto before use (e.g., run class-validator validation and
return 400 with validation errors when invalid) via a validateOr400 helper that
inspects validation errors and responds with res.status(400).json({ errors: ...
}); 2) after the use case returns a user, convert it to a safe representation
using a toSafeUser helper that strips sensitive fields (password,
verificationToken, resetToken, etc.) and only returns public fields (id, email,
name, createdAt, etc.); 3) centralize error mapping with a handleError helper
that maps domain errors to appropriate HTTP statuses (e.g., 409 for conflict,
404 for not found, 400 for bad request, 500 for unexpected) and sends consistent
JSON error responses; and 4) update createUser to call validateOr400(req,
userDto, res) before executing the use case, wrap the use case call in try/catch
that delegates to handleError on failure, and respond with
res.status(201).json(toSafeUser(user)) on success.


async getUserById(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const user = await this.getUserByIdUseCase.execute(id);
if (!user) {
res.status(404).json({ error: "User not found" });
return;
}
res.status(200).json(user);
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Comment on lines +42 to +56
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix unreachable 404 branch; validate id param; return safe payload; unify error handling.

GetUserByIdUseCase throws on missing/not-found, so the if (!user) 404 block never runs. Also, guard for missing id and sanitize response.

   async getUserById(req: Request, res: Response): Promise<void> {
     try {
-      const { id } = req.params;
-      const user = await this.getUserByIdUseCase.execute(id);
-      if (!user) {
-        res.status(404).json({ error: "User not found" });
-        return;
-      }
-      res.status(200).json(user);
+      const { id } = req.params;
+      if (!id) {
+        res.status(400).json({ error: "User ID is required" });
+        return;
+      }
+      const user = await this.getUserByIdUseCase.execute(id);
+      res.status(200).json(this.toSafeUser(user));
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }

Committable suggestion skipped: line range outside the PR's diff.


async getUserByEmail(req: Request, res: Response): Promise<void> {
try {
const { email } = req.query;
if (!email) {
res.status(400).json({ error: "Email is required" });
return;
}
const user = await this.getUserByEmailUseCase.execute(email as string);
if (!user) {
res.status(404).json({ error: "User not found" });
return;
}
res.status(200).json(user);
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Comment on lines +58 to +76
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Harden query param parsing; remove unreachable 404; return safe payload; unify error handling.

req.query.email can be a string or string[]; the current as string cast is unsafe. The use-case throws on not found, so the if (!user) branch is redundant.

   async getUserByEmail(req: Request, res: Response): Promise<void> {
     try {
-      const { email } = req.query;
-      if (!email) {
-        res.status(400).json({ error: "Email is required" });
-        return;
-      }
-      const user = await this.getUserByEmailUseCase.execute(email as string);
-      if (!user) {
-        res.status(404).json({ error: "User not found" });
-        return;
-      }
-      res.status(200).json(user);
+      const raw = req.query.email;
+      const email =
+        typeof raw === "string" ? raw : Array.isArray(raw) ? raw[0] : "";
+      if (!email) {
+        res.status(400).json({ error: "Email is required" });
+        return;
+      }
+      const user = await this.getUserByEmailUseCase.execute(email);
+      res.status(200).json(this.toSafeUser(user));
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/user/presentation/controllers/UserController.ts around lines 58
to 76, tighten query param parsing and simplify response/error flows: validate
req.query.email safely (handle string | string[] | undefined) by extracting a
single string (e.g., if array take the first element) and check it's a non-empty
string before proceeding; remove the unreachable if (!user) 404 branch since the
use-case throws when not found; before res.json(user) sanitize the user object
to exclude sensitive fields (password, tokens, etc.) and only return a safe DTO;
unify error handling in the catch to map known error types/names from the
use-case to appropriate status codes (e.g., NotFound -> 404, Validation -> 400)
and default to 500 for unexpected errors, including returning a generic message
while logging the real error for diagnostics.


async updateUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const userDto = new UpdateUserDto();
Object.assign(userDto, req.body);
userDto.id = id;

await this.updateUserUseCase.execute(userDto);
res.status(200).json({ message: "User updated successfully" });
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Comment on lines +78 to +92
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Validate update DTO; guard missing id; unify error handling.

Without explicit validation, DTO decorators don’t run. Also, Prisma will throw on missing/invalid id; add a param guard.

   async updateUser(req: Request, res: Response): Promise<void> {
     try {
-      const { id } = req.params;
-      const userDto = new UpdateUserDto();
-      Object.assign(userDto, req.body);
-      userDto.id = id;
-
-      await this.updateUserUseCase.execute(userDto);
+      const { id } = req.params;
+      if (!id) {
+        res.status(400).json({ error: "User ID is required" });
+        return;
+      }
+      const userDto = Object.assign(new UpdateUserDto(), req.body);
+      userDto.id = id;
+      if (!(await this.validateOr400(userDto, res))) return;
+      await this.updateUserUseCase.execute(userDto);
       res.status(200).json({ message: "User updated successfully" });
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/user/presentation/controllers/UserController.ts around lines 78
to 92, the updateUser method fails to run DTO validation and doesn't guard the
id param: ensure the UpdateUserDto is validated (e.g., call class-validator's
validate or validateOrReject on userDto after Object.assign and before executing
the use case), add an explicit check that req.params.id exists and is in the
expected format (return 400 with a clear message if missing/invalid) instead of
relying on Prisma to throw, and unify error handling by mapping
validation/client errors to 400 and unexpected errors to 500 (include
error.message in logs but return a generic message for 500 responses).


async deleteUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
await this.deleteUserUseCase.execute(id);
res.status(200).json({ message: "User deleted successfully" });
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
Comment on lines +94 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard missing id; map known errors (404/409) via centralized handler.

Keep 200 for compatibility, but ensure param validation and consistent error mapping.

   async deleteUser(req: Request, res: Response): Promise<void> {
     try {
-      const { id } = req.params;
-      await this.deleteUserUseCase.execute(id);
+      const { id } = req.params;
+      if (!id) {
+        res.status(400).json({ error: "User ID is required" });
+        return;
+      }
+      await this.deleteUserUseCase.execute(id);
       res.status(200).json({ message: "User deleted successfully" });
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async deleteUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
await this.deleteUserUseCase.execute(id);
res.status(200).json({ message: "User deleted successfully" });
} catch (error: unknown) {
res.status(400).json({
error: error instanceof Error ? error.message : "Unknown error",
});
}
}
async deleteUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
if (!id) {
res.status(400).json({ error: "User ID is required" });
return;
}
await this.deleteUserUseCase.execute(id);
res.status(200).json({ message: "User deleted successfully" });
} catch (error: unknown) {
this.handleError(res, error);
}
}
🤖 Prompt for AI Agents
In src/modules/user/presentation/controllers/UserController.ts around lines 94
to 104, the deleteUser handler lacks validation of the id param and bypasses the
centralized error mapper; first validate that req.params.id exists and return a
400 response immediately if missing, then change the signature to accept next
(req, res, next) and, inside the catch, call next(error) instead of responding
directly so the centralized error handler can map domain errors to 404/409; keep
the 200 JSON response on success.

}
Comment on lines +94 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add centralized helpers for safe responses, validation, and error mapping (P2002 → 409, P2025/not found → 404).

Reduces duplication and enforces consistent status codes aligned with the intended API.

   async deleteUser(req: Request, res: Response): Promise<void> {
     try {
       const { id } = req.params;
       if (!id) {
         res.status(400).json({ error: "User ID is required" });
         return;
       }
       await this.deleteUserUseCase.execute(id);
       res.status(200).json({ message: "User deleted successfully" });
     } catch (error: unknown) {
-      res.status(400).json({
-        error: error instanceof Error ? error.message : "Unknown error",
-      });
+      this.handleError(res, error);
     }
   }
+
+  // Centralized helpers
+  private toSafeUser(user: any) {
+    if (!user) return user;
+    const { password, verificationToken, verificationTokenExpires, ...safe } = user;
+    return safe;
+  }
+
+  private async validateOr400(dto: object, res: Response): Promise<boolean> {
+    const errors = await validate(dto as any);
+    if (errors.length > 0) {
+      res.status(400).json({
+        errors: errors.map((e: any) => ({
+          field: e.property,
+          constraints: e.constraints ?? {},
+        })),
+      });
+      return false;
+    }
+    return true;
+  }
+
+  private handleError(res: Response, error: unknown): void {
+    if (error && typeof error === "object" && "code" in (error as any)) {
+      const code = (error as any).code;
+      if (code === "P2002") {
+        res.status(409).json({ error: "Email already in use" });
+        return;
+      }
+      if (code === "P2025") {
+        res.status(404).json({ error: "User not found" });
+        return;
+      }
+    }
+    if (error instanceof Error && /not found/i.test(error.message)) {
+      res.status(404).json({ error: error.message });
+      return;
+    }
+    res.status(400).json({
+      error: error instanceof Error ? error.message : "Unknown error",
+    });
+  }
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/modules/user/presentation/controllers/UserController.ts around lines 94
to 105, replace the inline try/catch and direct res.json usage with centralized
helpers: validate the id param with the existing validation helper (return a 400
via the safe response helper on invalid input), call deleteUserUseCase, then
return success via the safeResponse helper; in the catch, pass the thrown error
into the shared error-mapping utility so DB/ORM errors map P2002 → 409 and
P2025/not-found → 404 (fallback to 500 or use error.message for unknown errors)
and send the mapped status and payload via the safe response helper to ensure
consistent responses across controllers.

This file was deleted.

This file was deleted.

Loading