-
Notifications
You must be signed in to change notification settings - Fork 71
fix: added supabase docs #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1a63970
56d3602
d2262c8
b3126e8
0cc26fa
97c3cad
c9c2cc5
2379ffd
e98f4e3
f126f8e
27a1094
e9e3d29
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,56 @@ src/modules/<domain>/ | |||||||||||||||||||||||||||||||||||||||||||||
| - **Containerization:** Docker, Docker Compose | ||||||||||||||||||||||||||||||||||||||||||||||
| - **Architecture:** Domain-Driven Design (DDD) | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## π¨ Error Handling & Exceptions | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| VolunChain uses a standardized error handling system with domain-specific exceptions: | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Allowed Exceptions | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| - **ValidationException (400)**: Invalid input data or DTO validation failures | ||||||||||||||||||||||||||||||||||||||||||||||
| - **AuthenticationException (401)**: Invalid credentials, missing tokens, or authentication failures | ||||||||||||||||||||||||||||||||||||||||||||||
| - **AuthorizationException (403)**: Insufficient permissions or access denied | ||||||||||||||||||||||||||||||||||||||||||||||
| - **ConflictException (409)**: Resource conflicts (e.g., duplicate emails, unique constraint violations) | ||||||||||||||||||||||||||||||||||||||||||||||
| - **InternalServerException (500)**: Unexpected system errors or database failures | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Error Response Format | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| All errors follow this consistent JSON structure: | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ```json | ||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||
| "statusCode": 400, | ||||||||||||||||||||||||||||||||||||||||||||||
| "errorCode": "VALIDATION_ERROR", | ||||||||||||||||||||||||||||||||||||||||||||||
| "message": "DTO validation failed", | ||||||||||||||||||||||||||||||||||||||||||||||
| "details": { "errors": [...] }, | ||||||||||||||||||||||||||||||||||||||||||||||
| "traceId": "abc-123-def" | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Implementation Rules | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| 1. **Controllers**: Never throw raw `Error` objects or craft custom JSON responses | ||||||||||||||||||||||||||||||||||||||||||||||
| 2. **Use Cases**: Surface domain exceptions only, no ad-hoc error handling | ||||||||||||||||||||||||||||||||||||||||||||||
| 3. **Repositories**: Use `prismaGuard()` wrapper to catch and map Prisma errors | ||||||||||||||||||||||||||||||||||||||||||||||
| 4. **Global Handler**: All exceptions are processed by the unified error handler | ||||||||||||||||||||||||||||||||||||||||||||||
| 5. **Validation**: DTO failures automatically become `ValidationException` | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Example Usage | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||||||||
| // β Correct - Throw domain exceptions | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw new ValidationException("User not found"); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // β Incorrect - Raw errors or custom responses | ||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("User not found"); | ||||||||||||||||||||||||||||||||||||||||||||||
| // or | ||||||||||||||||||||||||||||||||||||||||||||||
| res.status(400).json({ error: "User not found" }); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+124
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix exception type in example. βUser not foundβ β NotFoundException (404), not ValidationException. -// β
Correct - Throw domain exceptions
-if (!user) {
- throw new ValidationException("User not found");
-}
+// β
Correct - Throw domain exceptions
+if (!user) {
+ throw new NotFoundException("User not found");
+}π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## π Quick Start | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -236,6 +286,19 @@ npm run db:seed | |||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## π Supabase Integration | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| This project uses Supabase for external data access and future integrations. | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Update your `.env` file with: | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||
| SUPABASE_URL=... | ||||||||||||||||||||||||||||||||||||||||||||||
| SUPABASE_ANON_KEY=... | ||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+289
to
+298
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Verification agent π§© Analysis chainVerify variables are used in code and present in sample envs Ensure the code expects these exact names and that .env.example/.env.test include them. π Script executed: #!/bin/bash
# Find references to Supabase env vars across the repo
rg -nP '\bSUPABASE_(URL|ANON_KEY|SERVICE_ROLE_KEY)\b' -C2
# Confirm they exist in sample env files
fd -H '^\.env(\.example|\.test)?$' -a | xargs -I{} sh -c 'echo "---- {}"; cat "{}" | rg -n "SUPABASE_" || true'
# Check docker compose files for propagation
fd -a 'docker-compose*.yml' | xargs -I{} sh -c 'echo "---- {}"; rg -n "SUPABASE_" "{}" || true'Length of output: 1122 Add missing Supabase env vars to sample env files
π§° Toolsπͺ LanguageTool[grammar] ~239-~239: There might be a mistake here. (QB_NEW_EN) |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+289
to
+299
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion Avoid README/readme duplication (case conflict). Both README.md and readme.md exist; this breaks clones on case-insensitive FS (macOS/Windows) and confuses links. Keep a single README.md. π§° Toolsπͺ LanguageTool[grammar] ~289-~289: There might be a mistake here. (QB_NEW_EN) π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ## π Module Overview | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| ### Core Modules | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -83,6 +83,56 @@ src/modules/<domain>/ | |||||||||||||||||||||||||||||||||||||||
| - **Containerization:** Docker, Docker Compose | ||||||||||||||||||||||||||||||||||||||||
| - **Architecture:** Domain-Driven Design (DDD) | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ## π¨ Error Handling & Exceptions | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| VolunChain uses a standardized error handling system with domain-specific exceptions: | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ### Allowed Exceptions | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| - **ValidationException (400)**: Invalid input data or DTO validation failures | ||||||||||||||||||||||||||||||||||||||||
| - **AuthenticationException (401)**: Invalid credentials, missing tokens, or authentication failures | ||||||||||||||||||||||||||||||||||||||||
| - **AuthorizationException (403)**: Insufficient permissions or access denied | ||||||||||||||||||||||||||||||||||||||||
| - **ConflictException (409)**: Resource conflicts (e.g., duplicate emails, unique constraint violations) | ||||||||||||||||||||||||||||||||||||||||
| - **InternalServerException (500)**: Unexpected system errors or database failures | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ### Error Response Format | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| All errors follow this consistent JSON structure: | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ```json | ||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||
| "statusCode": 400, | ||||||||||||||||||||||||||||||||||||||||
| "errorCode": "VALIDATION_ERROR", | ||||||||||||||||||||||||||||||||||||||||
| "message": "DTO validation failed", | ||||||||||||||||||||||||||||||||||||||||
| "details": { "errors": [...] }, | ||||||||||||||||||||||||||||||||||||||||
| "traceId": "abc-123-def" | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ### Implementation Rules | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| 1. **Controllers**: Never throw raw `Error` objects or craft custom JSON responses | ||||||||||||||||||||||||||||||||||||||||
| 2. **Use Cases**: Surface domain exceptions only, no ad-hoc error handling | ||||||||||||||||||||||||||||||||||||||||
| 3. **Repositories**: Use `prismaGuard()` wrapper to catch and map Prisma errors | ||||||||||||||||||||||||||||||||||||||||
| 4. **Global Handler**: All exceptions are processed by the unified error handler | ||||||||||||||||||||||||||||||||||||||||
| 5. **Validation**: DTO failures automatically become `ValidationException` | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ### Example Usage | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ```typescript | ||||||||||||||||||||||||||||||||||||||||
| // β Correct - Throw domain exceptions | ||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||
| throw new ValidationException("User not found"); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // β Incorrect - Raw errors or custom responses | ||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||
| throw new Error("User not found"); | ||||||||||||||||||||||||||||||||||||||||
| // or | ||||||||||||||||||||||||||||||||||||||||
| res.status(400).json({ error: "User not found" }); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+120
to
+134
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example should not use ValidationException for missing user. βUser not foundβ maps to NotFound (404), not Validation (400). -// β
Correct - Throw domain exceptions
-if (!user) {
- throw new ValidationException("User not found");
-}
+// β
Correct - Throw domain exceptions
+if (!user) {
+ throw new NotFoundException("User not found");
+}π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| ## π Quick Start | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,4 @@ | ||||||||||||||||||||||||
| import { Request, Response } from "express"; | ||||||||||||||||||||||||
| import { Request, Response, NextFunction } from "express"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // imports for DTO validator | ||||||||||||||||||||||||
| import { plainToInstance } from "class-transformer"; | ||||||||||||||||||||||||
|
|
@@ -21,6 +21,9 @@ import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase"; | |||||||||||||||||||||||
| import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase"; | ||||||||||||||||||||||||
| import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Domain exceptions | ||||||||||||||||||||||||
| import { ValidationException, AuthenticationException, InternalServerException } from "../../../../shared/exceptions"; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const userRepository = new PrismaUserRepository(); | ||||||||||||||||||||||||
| const sendVerificationEmailUseCase = new SendVerificationEmailUseCase( | ||||||||||||||||||||||||
| userRepository | ||||||||||||||||||||||||
|
|
@@ -35,130 +38,116 @@ const verifyWalletUseCase = new VerifyWalletUseCase(); | |||||||||||||||||||||||
| // DTO validator | ||||||||||||||||||||||||
| async function validateOr400<T>( | ||||||||||||||||||||||||
| Cls: new () => T, | ||||||||||||||||||||||||
| payload: unknown, | ||||||||||||||||||||||||
| res: Response | ||||||||||||||||||||||||
| ): Promise<T | undefined> { | ||||||||||||||||||||||||
| payload: unknown | ||||||||||||||||||||||||
| ): Promise<T> { | ||||||||||||||||||||||||
| const dto = plainToInstance(Cls, payload); | ||||||||||||||||||||||||
| const errors = await validate(dto as object, { | ||||||||||||||||||||||||
| whitelist: true, | ||||||||||||||||||||||||
| forbidNonWhitelisted: true, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // dto not verified, throw a Bad Request | ||||||||||||||||||||||||
| // DTO not verified, throw ValidationException | ||||||||||||||||||||||||
| if (errors.length) { | ||||||||||||||||||||||||
| res.status(400).json({ message: "Validation failed", errors }); | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| const formattedErrors = errors.map(error => ({ | ||||||||||||||||||||||||
| property: error.property, | ||||||||||||||||||||||||
| value: error.value, | ||||||||||||||||||||||||
| constraints: error.constraints ? Object.values(error.constraints) : [], | ||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||
| throw new ValidationException('DTO validation failed', { errors: formattedErrors }); | ||||||||||||||||||||||||
|
Comment on lines
+51
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not include raw input values in validation error details (PII leak).
- const formattedErrors = errors.map(error => ({
- property: error.property,
- value: error.value,
- constraints: error.constraints ? Object.values(error.constraints) : [],
- }));
+ const formattedErrors = errors.map(error => ({
+ property: error.property,
+ constraints: error.constraints ? Object.values(error.constraints) : [],
+ }));π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| return dto; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const register = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π‘ Verification agent π§© Analysis chainEnsure errors reach the global handler: pass Throwing inside async handlers is only auto-forwarded in Express 5 (or with If youβre already on Express 5 or using -const register = async (req: Request, res: Response) => {
+const register = async (req: Request, res: Response, next: NextFunction) => {
try {
const dto = await validateOr400(RegisterDto, req.body);
await sendVerificationEmailUseCase.execute({ email: dto.email });
res.status(200).json({ message: "Verification email sent" });
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};
-const login = async (req: Request, res: Response) => {
+const login = async (req: Request, res: Response, next: NextFunction) => {
try {
const dto = await validateOr400(LoginDto, req.body);
// TODO: Implement Wallet auth logic as a use case
throw new InternalServerException('Login service temporarily disabled', {
error: "Wallet auth logic not implemented yet"
});
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};
-const resendVerificationEmail = async (req: Request, res: Response) => {
+const resendVerificationEmail = async (req: Request, res: Response, next: NextFunction) => {
try {
const dto = await validateOr400(ResendVerificationDTO, req.body);
await resendVerificationEmailUseCase.execute({ email: dto.email });
res.status(200).json({ message: "Verification email resent" });
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};
-const verifyEmail = async (req: Request, res: Response) => {
+const verifyEmail = async (req: Request, res: Response, next: NextFunction) => {
try {
// ...
const result = await verifyEmailUseCase.execute({ token });
const status = result.success ? 200 : 400;
res.status(status).json(result);
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};
-const verifyWallet = async (req: Request, res: Response) => {
+const verifyWallet = async (req: Request, res: Response, next: NextFunction) => {
try {
const dto = await validateOr400(VerifyWalletDto, req.body);
const result = await verifyWalletUseCase.execute(dto);
const status = result.verified ? 200 : 400;
res.status(status).json(result);
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};
-const validateWalletFormat = async (req: Request, res: Response) => {
+const validateWalletFormat = async (req: Request, res: Response, next: NextFunction) => {
try {
const dto = await validateOr400(ValidateWalletFormatDto, req.body);
const result = await validateWalletFormatUseCase.execute(dto);
const status = result.valid ? 200 : 400;
res.status(status).json(result);
} catch (err) {
- // Let the global error handler deal with it
- throw err;
+ next(err);
}
};Verify Express version and presence of Also applies to: 69-72, 75-75, 83-86, 89-89, 96-99, 102-102, 121-124, 127-127, 134-137, 140-140, 148-151 π Script executed: #!/bin/bash
# Show Express version and whether express-async-errors is used
jq -r '.dependencies.express, .devDependencies.express' package.json
jq -r '.dependencies["express-async-errors"], .devDependencies["express-async-errors"]' package.jsonLength of output: 196 Add explicit error forwarding in async controllers π€ Prompt for AI Agents |
||||||||||||||||||||||||
| const dto = await validateOr400(RegisterDto, req.body, res); | ||||||||||||||||||||||||
| if (!dto) return; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const dto = await validateOr400(RegisterDto, req.body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Send verification email to provided address | ||||||||||||||||||||||||
| await sendVerificationEmailUseCase.execute({ email: dto.email }); | ||||||||||||||||||||||||
| res.status(200).json({ message: "Verification email sent" }); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| const message = | ||||||||||||||||||||||||
| err instanceof Error ? err.message : "Failed to send verification email"; | ||||||||||||||||||||||||
| const status = message === "User not found" ? 400 : 500; | ||||||||||||||||||||||||
| res.status(status).json({ error: message }); | ||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const login = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
| const dto = await validateOr400(LoginDto, req.body, res); | ||||||||||||||||||||||||
| if (!dto) return; | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const dto = await validateOr400(LoginDto, req.body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // TODO: Implement Wallet auth logic as a use case | ||||||||||||||||||||||||
| res.status(501).json({ | ||||||||||||||||||||||||
| message: "Login service temporarily disabled", | ||||||||||||||||||||||||
| error: "Wallet auth logic not implemented yet", | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| // TODO: Implement Wallet auth logic as a use case | ||||||||||||||||||||||||
| throw new InternalServerException('Login service temporarily disabled', { | ||||||||||||||||||||||||
| error: "Wallet auth logic not implemented yet" | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const resendVerificationEmail = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
| const dto = await validateOr400(ResendVerificationDTO, req.body, res); | ||||||||||||||||||||||||
| if (!dto) return; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const dto = await validateOr400(ResendVerificationDTO, req.body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Resends verification email to provided address | ||||||||||||||||||||||||
| await resendVerificationEmailUseCase.execute({ email: dto.email }); | ||||||||||||||||||||||||
| res.status(200).json({ message: "Verification email resent" }); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| const message = | ||||||||||||||||||||||||
| err instanceof Error | ||||||||||||||||||||||||
| ? err.message | ||||||||||||||||||||||||
| : "Failed to resend verification email"; | ||||||||||||||||||||||||
| const status = message === "User not found" ? 404 : 500; | ||||||||||||||||||||||||
| res.status(status).json({ error: message }); | ||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const verifyEmail = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
| const tokenParam = | ||||||||||||||||||||||||
| typeof req.params.token === "string" ? req.params.token : undefined; | ||||||||||||||||||||||||
| const tokenQuery = | ||||||||||||||||||||||||
| typeof req.query.token === "string" | ||||||||||||||||||||||||
| ? (req.query.token as string) | ||||||||||||||||||||||||
| : undefined; | ||||||||||||||||||||||||
| const token = tokenParam || tokenQuery; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // if token is not given in the request | ||||||||||||||||||||||||
| if (!token) { | ||||||||||||||||||||||||
| res.status(400).json({ | ||||||||||||||||||||||||
| success: false, | ||||||||||||||||||||||||
| message: "Token in URL is required", | ||||||||||||||||||||||||
| verified: false, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const tokenParam = | ||||||||||||||||||||||||
| typeof req.params.token === "string" ? req.params.token : undefined; | ||||||||||||||||||||||||
| const tokenQuery = | ||||||||||||||||||||||||
| typeof req.query.token === "string" | ||||||||||||||||||||||||
| ? (req.query.token as string) | ||||||||||||||||||||||||
| : undefined; | ||||||||||||||||||||||||
| const token = tokenParam || tokenQuery; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // if token is not given in the request | ||||||||||||||||||||||||
| if (!token) { | ||||||||||||||||||||||||
| throw new ValidationException('Token in URL is required'); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Verifies email using use case | ||||||||||||||||||||||||
| const result = await verifyEmailUseCase.execute({ token }); | ||||||||||||||||||||||||
| const status = result.success ? 200 : 400; | ||||||||||||||||||||||||
| res.status(status).json(result); | ||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||
| res.status(400).json({ | ||||||||||||||||||||||||
| success: false, | ||||||||||||||||||||||||
| message: "Invalid or expired verification token", | ||||||||||||||||||||||||
| verified: false, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
|
Comment on lines
117
to
+121
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. π οΈ Refactor suggestion Use domain exceptions for negative paths to keep responses unified. Instead of returning 400 JSON with ad-hoc shapes, throw domain exceptions and let the global error handler format the response. - const result = await verifyEmailUseCase.execute({ token });
- const status = result.success ? 200 : 400;
- res.status(status).json(result);
+ const result = await verifyEmailUseCase.execute({ token });
+ if (!result.success) {
+ throw new ValidationException("Email verification failed");
+ }
+ res.status(200).json(result);- const result = await verifyWalletUseCase.execute(dto);
- const status = result.verified ? 200 : 400;
- res.status(status).json(result);
+ const result = await verifyWalletUseCase.execute(dto);
+ if (!result.verified) {
+ throw new ValidationException("Wallet verification failed");
+ }
+ res.status(200).json(result);- const result = await validateWalletFormatUseCase.execute(dto);
- const status = result.valid ? 200 : 400;
- res.status(status).json(result);
+ const result = await validateWalletFormatUseCase.execute(dto);
+ if (!result.valid) {
+ throw new ValidationException("Invalid wallet format");
+ }
+ res.status(200).json(result);Also applies to: 131-133, 145-147 |
||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const verifyWallet = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
| const dto = await validateOr400(VerifyWalletDto, req.body, res); | ||||||||||||||||||||||||
| if (!dto) return; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const dto = await validateOr400(VerifyWalletDto, req.body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const result = await verifyWalletUseCase.execute(dto); | ||||||||||||||||||||||||
| const status = result.verified ? 200 : 400; | ||||||||||||||||||||||||
| res.status(status).json(result); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| const message = | ||||||||||||||||||||||||
| err instanceof Error ? err.message : "Wallet verification failed"; | ||||||||||||||||||||||||
| res.status(500).json({ error: message }); | ||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const validateWalletFormat = async (req: Request, res: Response) => { | ||||||||||||||||||||||||
| const dto = await validateOr400(ValidateWalletFormatDto, req.body, res); | ||||||||||||||||||||||||
| if (!dto) return; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const dto = await validateOr400(ValidateWalletFormatDto, req.body); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Validates wallet format using use case | ||||||||||||||||||||||||
| const result = await validateWalletFormatUseCase.execute(dto); | ||||||||||||||||||||||||
| const status = result.valid ? 200 : 400; | ||||||||||||||||||||||||
| res.status(status).json(result); | ||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||
| const message = | ||||||||||||||||||||||||
| err instanceof Error ? err.message : "Wallet format validation failed"; | ||||||||||||||||||||||||
| res.status(500).json({ error: message }); | ||||||||||||||||||||||||
| // Let the global error handler deal with it | ||||||||||||||||||||||||
| throw err; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,39 +1,19 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IUserRepository } from "../../../repository/IUserRepository"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { randomBytes } from "crypto"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { sendVerificationEmail } from "../../../utils/email.utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { IUserRepository } from "../../user/domain/interfaces/IUserRepository"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ValidationException, ConflictException } from "../../../../shared/exceptions"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class EmailVerificationUseCase { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(private userRepository: IUserRepository) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async sendVerificationEmail(email: string): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async execute({ email }: { email: string }): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await this.userRepository.findByEmail(email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) throw new Error("User not found"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (user.isVerified) throw new Error("User is already verified"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const token = randomBytes(32).toString("hex"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const expires = new Date(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| expires.setHours(expires.getHours() + 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.userRepository.updateVerificationToken(user.id, token, expires); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const verificationLink = `http://localhost:3000/auth/verify-email?token=${token}`; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await sendVerificationEmail(user.email, verificationLink); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async verifyEmail(token: string): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const user = await this.userRepository.findByVerificationToken(token); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) throw new Error("Invalid or expired token"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| user.verificationTokenExpires && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new Date() > user.verificationTokenExpires | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "Token expired. Please request a new verification email." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!user) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ValidationException("User not found"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await this.userRepository.updateVerificationStatus(user.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (user.isVerified) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new ConflictException("User is already verified"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid user enumeration in verification initiation. Same pattern: do not error on unknown or already-verified emails. Return 202/void regardless and no-op. - const user = await this.userRepository.findByEmail(email);
- if (!user) {
- throw new ValidationException("User not found");
- }
- if (user.isVerified) {
- throw new ConflictException("User is already verified");
- }
+ const user = await this.userRepository.findByEmail(email);
+ if (!user || user.isVerified) {
+ return; // No-op to prevent enumeration
+ }π Committable suggestion
Suggested change
π€ Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // TODO: Implement email verification logic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // For now, just validate user state | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
π οΈ Refactor suggestion
Include NotFoundException (404) in the allowed list.
Docs omit 404 while the code exports NotFoundException. Add it for consistency.
- **ConflictException (409)**: Resource conflicts (e.g., duplicate emails, unique constraint violations) - **InternalServerException (500)**: Unexpected system errors or database failures +- **NotFoundException (404)**: Requested resource does not existπ§° Tools
πͺ LanguageTool
[grammar] ~92-~92: There might be a mistake here.
Context: ...id input data or DTO validation failures - AuthenticationException (401): Invalid...
(QB_NEW_EN)
[grammar] ~93-~93: There might be a mistake here.
Context: ...ssing tokens, or authentication failures - AuthorizationException (403): Insuffic...
(QB_NEW_EN)
[grammar] ~94-~94: There might be a mistake here.
Context: ...nsufficient permissions or access denied - ConflictException (409): Resource conf...
(QB_NEW_EN)
[grammar] ~95-~95: There might be a mistake here.
Context: ...te emails, unique constraint violations) - InternalServerException (500): Unexpec...
(QB_NEW_EN)
π€ Prompt for AI Agents