feat: internationalize API error responses by locale
Add shared error codes with zh/en/ms messages, coded app exceptions, and locale-aware global filter. Frontends send X-Locale so error text matches the active UI language. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, ForbiddenException } from '@nestjs/common';
|
||||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { IS_PUBLIC_KEY, PERMISSIONS_KEY } from '../../shared/common/decorators';
|
||||
import { appForbidden, appUnauthorized } from '../../shared/common/app-error';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
@@ -19,7 +20,7 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
|
||||
}
|
||||
|
||||
handleRequest<TUser = unknown>(err: Error | null, user: TUser): TUser {
|
||||
if (err || !user) throw err || new UnauthorizedException();
|
||||
if (err || !user) throw err || appUnauthorized('INVALID_CREDENTIALS');
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -36,17 +37,17 @@ export class PermissionsGuard implements CanActivate {
|
||||
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (!user || user.userType !== 'ADMIN') {
|
||||
throw new ForbiddenException('Admin access required');
|
||||
throw appForbidden('ADMIN_ACCESS_REQUIRED');
|
||||
}
|
||||
if (user.role === 'SUPER_ADMIN') return true;
|
||||
|
||||
if (!required?.length) {
|
||||
throw new ForbiddenException('Insufficient permissions');
|
||||
throw appForbidden('INSUFFICIENT_PERMISSIONS');
|
||||
}
|
||||
|
||||
const userPerms: string[] = user.permissions ?? [];
|
||||
const hasAccess = required.some((p) => userPerms.includes(p));
|
||||
if (!hasAccess) throw new ForbiddenException('Insufficient permissions');
|
||||
if (!hasAccess) throw appForbidden('INSUFFICIENT_PERMISSIONS');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -57,7 +58,7 @@ export function UserTypeGuard(...types: string[]) {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (!types.includes(user?.userType)) {
|
||||
throw new ForbiddenException('Access denied for this portal');
|
||||
throw appForbidden('ACCESS_DENIED_PORTAL');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -69,7 +70,7 @@ export function UserTypeGuard(...types: string[]) {
|
||||
export class PlayerGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (user?.userType !== 'PLAYER') throw new ForbiddenException('Player access only');
|
||||
if (user?.userType !== 'PLAYER') throw appForbidden('PLAYER_ACCESS_ONLY');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -78,7 +79,7 @@ export class PlayerGuard implements CanActivate {
|
||||
export class AdminGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (user?.userType !== 'ADMIN') throw new ForbiddenException('Admin access only');
|
||||
if (user?.userType !== 'ADMIN') throw appForbidden('ADMIN_ACCESS_ONLY');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export class AdminGuard implements CanActivate {
|
||||
export class AgentGuard implements CanActivate {
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const { user } = context.switchToHttp().getRequest();
|
||||
if (user?.userType !== 'AGENT') throw new ForbiddenException('Agent access only');
|
||||
if (user?.userType !== 'AGENT') throw appForbidden('AGENT_ACCESS_ONLY');
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user