From 3368ab3b26900166a74687d8a1d9f46f6c36f8d5 Mon Sep 17 00:00:00 2001 From: Big-cedar Date: Fri, 3 Oct 2025 23:33:28 +0100 Subject: [PATCH 1/2] fix: resolve lint errors for API consistency --- package-lock.json | 13 ++- src/cache/controllers/cache.controller.ts | 19 ++-- .../controllers/attributes.controller.ts | 97 ++++------------- .../auth/controllers/auth.controller.ts | 103 +++++------------- src/modules/auth/services/auth.service.ts | 4 +- .../coupons/controllers/coupon.controller.ts | 13 ++- .../escrow/controllers/escrow.controller.ts | 83 +++----------- .../controllers/notification.controller.ts | 7 +- src/modules/offers/offfer.controller.ts | 9 +- .../controllers/product.controller.ts | 15 +-- .../seller/controllers/seller.controller.ts | 35 ++---- src/modules/seller/tests/seller.e2e.spec.ts | 41 ++++--- .../stores/controllers/store.controller.ts | 55 ++-------- .../users/controllers/user.controller.ts | 31 ++++-- test/auth.e2e-spec.ts | 4 + 15 files changed, 183 insertions(+), 346 deletions(-) diff --git a/package-lock.json b/package-lock.json index cadfaa4..6ca8ca0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,8 @@ "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "zod": "^3.22.4" } }, "node_modules/@ampproject/remapping": { @@ -19163,6 +19164,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/src/cache/controllers/cache.controller.ts b/src/cache/controllers/cache.controller.ts index 046519e..bf12ae6 100644 --- a/src/cache/controllers/cache.controller.ts +++ b/src/cache/controllers/cache.controller.ts @@ -1,5 +1,6 @@ import { Controller, Post, Get, Delete, UseGuards, HttpCode, HttpStatus } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../common/decorators/api-response.decorator'; import { CacheService } from '../cache.service'; import { AuthGuard } from '../../modules/shared/guards/auth.guard'; import { RolesGuard } from '../../modules/shared/guards/roles.guard'; @@ -15,8 +16,8 @@ export class CacheController { @Get('stats') @Roles('admin') @ApiOperation({ summary: 'Get cache statistics' }) - @ApiResponse({ status: 200, description: 'Cache statistics retrieved successfully' }) - @ApiResponse({ status: 403, description: 'Forbidden - Admin access required' }) + @ApiSuccessResponse(200, 'Cache statistics retrieved successfully') + @ApiErrorResponse(403, 'Forbidden - Admin access required') async getStats() { return await this.cacheService.getStats(); } @@ -25,8 +26,8 @@ export class CacheController { @Roles('admin') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Clear entire cache' }) - @ApiResponse({ status: 200, description: 'Cache cleared successfully' }) - @ApiResponse({ status: 403, description: 'Forbidden - Admin access required' }) + @ApiSuccessResponse(200, 'Cache cleared successfully') + @ApiErrorResponse(403, 'Forbidden - Admin access required') async resetCache() { await this.cacheService.reset(); return { message: 'Cache cleared successfully' }; @@ -36,8 +37,8 @@ export class CacheController { @Roles('admin') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Invalidate cache for specific entity' }) - @ApiResponse({ status: 200, description: 'Entity cache invalidated successfully' }) - @ApiResponse({ status: 403, description: 'Forbidden - Admin access required' }) + @ApiSuccessResponse(200, 'Entity cache invalidated successfully') + @ApiErrorResponse(403, 'Forbidden - Admin access required') async invalidateEntity(entity: string) { await this.cacheService.invalidateEntity(entity); return { message: `Cache invalidated for entity: ${entity}` }; @@ -47,8 +48,8 @@ export class CacheController { @Roles('admin') @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Invalidate cache for specific entity action' }) - @ApiResponse({ status: 200, description: 'Entity action cache invalidated successfully' }) - @ApiResponse({ status: 403, description: 'Forbidden - Admin access required' }) + @ApiSuccessResponse(200, 'Entity action cache invalidated successfully') + @ApiErrorResponse(403, 'Forbidden - Admin access required') async invalidateAction(entity: string, action: string) { await this.cacheService.invalidateAction(entity, action); return { message: `Cache invalidated for entity: ${entity}, action: ${action}` }; diff --git a/src/modules/attributes/controllers/attributes.controller.ts b/src/modules/attributes/controllers/attributes.controller.ts index bd41f74..0e9527c 100644 --- a/src/modules/attributes/controllers/attributes.controller.ts +++ b/src/modules/attributes/controllers/attributes.controller.ts @@ -11,7 +11,8 @@ import { HttpStatus, UseGuards, } from "@nestjs/common" -import { ApiTags, ApiOperation, ApiResponse, ApiParam, ApiQuery, ApiBearerAuth } from "@nestjs/swagger" +import { ApiTags, ApiOperation, ApiParam, ApiQuery, ApiBearerAuth } from "@nestjs/swagger" +import { ApiSuccessResponse, ApiErrorResponse } from "../../../common/decorators/api-response.decorator" import { CreateAttributeDto } from "../dto/create-attribute.dto" import { UpdateAttributeDto } from "../dto/update-attribute.dto" import { GetAttributesQueryDto } from "../dto/get-attributes-query.dto" @@ -33,30 +34,16 @@ export class AttributeController { @UseGuards(RolesGuard) // @Roles(UserRole.ADMIN, UserRole.MANAGER) @ApiOperation({ summary: 'Create a new attribute' }) - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Attribute created successfully', - type: AttributeResponseDto, - }) - @ApiResponse({ - status: HttpStatus.CONFLICT, - description: 'Attribute with this name already exists', - }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Invalid input data', - }) + @ApiSuccessResponse(HttpStatus.CREATED, 'Attribute created successfully', AttributeResponseDto) + @ApiErrorResponse(HttpStatus.CONFLICT, 'Attribute with this name already exists') + @ApiErrorResponse(HttpStatus.BAD_REQUEST, 'Invalid input data') async create(@Body() createAttributeDto: CreateAttributeDto) { return this.attributeService.create(createAttributeDto); } @Get() @ApiOperation({ summary: 'Get all attributes with pagination' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Attributes retrieved successfully', - type: PaginatedAttributesResponseDto, - }) + @ApiSuccessResponse(HttpStatus.OK, 'Attributes retrieved successfully', PaginatedAttributesResponseDto) @ApiQuery({ name: 'limit', required: false, type: Number }) @ApiQuery({ name: 'offset', required: false, type: Number }) @ApiQuery({ name: 'search', required: false, type: String }) @@ -67,15 +54,8 @@ export class AttributeController { @Get(':id') @ApiOperation({ summary: 'Get an attribute by ID' }) @ApiParam({ name: 'id', type: Number, description: 'Attribute ID' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Attribute retrieved successfully', - type: AttributeResponseDto, - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Attribute not found', - }) + @ApiSuccessResponse(HttpStatus.OK, 'Attribute retrieved successfully', AttributeResponseDto) + @ApiErrorResponse(HttpStatus.NOT_FOUND, 'Attribute not found') async findOne(@Param('id', ParseIntPipe) id: number) { return this.attributeService.findOne(id); } @@ -85,19 +65,9 @@ export class AttributeController { // @Roles(UserRole.ADMIN, UserRole.MANAGER) @ApiOperation({ summary: "Update an attribute" }) @ApiParam({ name: "id", type: Number, description: "Attribute ID" }) - @ApiResponse({ - status: HttpStatus.OK, - description: "Attribute updated successfully", - type: AttributeResponseDto, - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: "Attribute not found", - }) - @ApiResponse({ - status: HttpStatus.CONFLICT, - description: "Attribute with this name already exists", - }) + @ApiSuccessResponse(HttpStatus.OK, "Attribute updated successfully", AttributeResponseDto) + @ApiErrorResponse(HttpStatus.NOT_FOUND, "Attribute not found") + @ApiErrorResponse(HttpStatus.CONFLICT, "Attribute with this name already exists") async update(@Param('id', ParseIntPipe) id: number, @Body() updateAttributeDto: UpdateAttributeDto) { return this.attributeService.update(id, updateAttributeDto) } @@ -107,14 +77,8 @@ export class AttributeController { // @Roles(UserRole.ADMIN) @ApiOperation({ summary: 'Delete an attribute' }) @ApiParam({ name: 'id', type: Number, description: 'Attribute ID' }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Attribute deleted successfully', - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Attribute not found', - }) + @ApiSuccessResponse(HttpStatus.NO_CONTENT, 'Attribute deleted successfully') + @ApiErrorResponse(HttpStatus.NOT_FOUND, 'Attribute not found') async remove(@Param('id', ParseIntPipe) id: number) { await this.attributeService.remove(id); } @@ -124,18 +88,9 @@ export class AttributeController { // @Roles(UserRole.ADMIN, UserRole.MANAGER) @ApiOperation({ summary: "Add a value to an attribute" }) @ApiParam({ name: "id", type: Number, description: "Attribute ID" }) - @ApiResponse({ - status: HttpStatus.CREATED, - description: "Attribute value added successfully", - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: "Attribute not found", - }) - @ApiResponse({ - status: HttpStatus.CONFLICT, - description: "Value already exists for this attribute", - }) + @ApiSuccessResponse(HttpStatus.CREATED, "Attribute value added successfully") + @ApiErrorResponse(HttpStatus.NOT_FOUND, "Attribute not found") + @ApiErrorResponse(HttpStatus.CONFLICT, "Value already exists for this attribute") async addValue(@Param('id', ParseIntPipe) id: number, @Body('value') value: string) { return this.attributeService.addValue(id, value) } @@ -143,14 +98,8 @@ export class AttributeController { @Get(':id/values') @ApiOperation({ summary: 'Get all values for an attribute' }) @ApiParam({ name: 'id', type: Number, description: 'Attribute ID' }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Attribute values retrieved successfully', - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: 'Attribute not found', - }) + @ApiSuccessResponse(HttpStatus.OK, 'Attribute values retrieved successfully') + @ApiErrorResponse(HttpStatus.NOT_FOUND, 'Attribute not found') async getValues(@Param('id', ParseIntPipe) id: number) { return this.attributeService.getAttributeValues(id); } @@ -161,14 +110,8 @@ export class AttributeController { @ApiOperation({ summary: "Remove a value from an attribute" }) @ApiParam({ name: "id", type: Number, description: "Attribute ID" }) @ApiParam({ name: "valueId", type: Number, description: "Attribute Value ID" }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: "Attribute value removed successfully", - }) - @ApiResponse({ - status: HttpStatus.NOT_FOUND, - description: "Attribute or value not found", - }) + @ApiSuccessResponse(HttpStatus.NO_CONTENT, "Attribute value removed successfully") + @ApiErrorResponse(HttpStatus.NOT_FOUND, "Attribute or value not found") async removeValue(@Param('id', ParseIntPipe) id: number, @Param('valueId', ParseIntPipe) valueId: number) { await this.attributeService.removeValue(id, valueId) } diff --git a/src/modules/auth/controllers/auth.controller.ts b/src/modules/auth/controllers/auth.controller.ts index f075423..d571c8f 100644 --- a/src/modules/auth/controllers/auth.controller.ts +++ b/src/modules/auth/controllers/auth.controller.ts @@ -14,11 +14,11 @@ import { Request, Response } from 'express'; import { ApiTags, ApiOperation, - ApiResponse, ApiBody, ApiBearerAuth, ApiCookieAuth, } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse, ApiAuthResponse } from '../../../common/decorators/api-response.decorator'; import { AuthService } from '../services/auth.service'; import { BadRequestError } from '../../../utils/errors'; import { StellarWalletLoginDto, RegisterUserDto, ChallengeDto } from '../dto/auth.dto'; @@ -48,15 +48,8 @@ export class AuthController { 'Generate a challenge message for wallet authentication. The user must sign this message with their Stellar wallet to authenticate.', }) @ApiBody({ type: ChallengeDto }) - @ApiResponse({ - status: 200, - description: 'Challenge generated successfully', - type: ChallengeResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Invalid wallet address format', - }) + @ApiSuccessResponse(200, 'Challenge generated successfully', ChallengeResponseDto) + @ApiErrorResponse(400, 'Invalid wallet address format') async generateChallenge(@Body() challengeDto: ChallengeDto): Promise { const challenge = this.authService.generateChallenge(challengeDto.walletAddress); @@ -78,19 +71,9 @@ export class AuthController { 'Authenticate user using their Stellar wallet signature. The user must first get a challenge and sign it with their wallet.', }) @ApiBody({ type: StellarWalletLoginDto }) - @ApiResponse({ - status: 200, - description: 'Login successful', - type: AuthResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Invalid signature or wallet address', - }) - @ApiResponse({ - status: 401, - description: 'Authentication failed', - }) + @ApiAuthResponse(200, 'Login successful', AuthResponseDto) + @ApiErrorResponse(400, 'Invalid signature or wallet address') + @ApiErrorResponse(401, 'Authentication failed') async loginWithWallet( @Body() loginDto: StellarWalletLoginDto, @Res({ passthrough: true }) res: Response @@ -102,18 +85,16 @@ export class AuthController { maxAge: result.expiresIn * 1000, // Convert to milliseconds }); + // Return plain data; ResponseInterceptor will wrap and include token return { - success: true, - data: { - user: { - walletAddress: result.user.walletAddress, - name: result.user.name, - email: result.user.email, - role: result.user.userRoles?.[0]?.role?.name || 'buyer', - }, - expiresIn: result.expiresIn, + user: { + walletAddress: result.user.walletAddress, + name: result.user.name, + email: result.user.email, + role: result.user.userRoles?.[0]?.role?.name || 'buyer', }, - }; + expiresIn: result.expiresIn, + } as any; } /** @@ -128,19 +109,9 @@ export class AuthController { 'Register a new user using their Stellar wallet. User must specify their role (buyer or seller).', }) @ApiBody({ type: RegisterUserDto }) - @ApiResponse({ - status: 201, - description: 'User registered successfully', - type: AuthResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Invalid signature, wallet address, or user already exists', - }) - @ApiResponse({ - status: 409, - description: 'User already exists', - }) + @ApiAuthResponse(201, 'User registered successfully', AuthResponseDto) + @ApiErrorResponse(400, 'Invalid signature, wallet address, or user already exists') + @ApiErrorResponse(409, 'User already exists') async registerWithWallet( @Body() registerDto: RegisterUserDto, @Res({ passthrough: true }) res: Response @@ -158,18 +129,16 @@ export class AuthController { maxAge: result.expiresIn * 1000, // Convert to milliseconds }); + // Return plain data; ResponseInterceptor will wrap and include token return { - success: true, - data: { - user: { - walletAddress: result.user.walletAddress, - name: result.user.name, - email: result.user.email, - role: result.user.userRoles?.[0]?.role?.name || 'buyer', - }, - expiresIn: result.expiresIn, + user: { + walletAddress: result.user.walletAddress, + name: result.user.name, + email: result.user.email, + role: result.user.userRoles?.[0]?.role?.name || 'buyer', }, - }; + expiresIn: result.expiresIn, + } as any; } /** @@ -185,15 +154,8 @@ export class AuthController { }) @ApiBearerAuth() @ApiCookieAuth() - @ApiResponse({ - status: 200, - description: 'User information retrieved successfully', - type: UserResponseDto, - }) - @ApiResponse({ - status: 401, - description: 'User not authenticated', - }) + @ApiSuccessResponse(200, 'User information retrieved successfully', UserResponseDto) + @ApiErrorResponse(401, 'User not authenticated') async getMe(@Req() req: Request): Promise { const userId = req.user?.id; if (!userId) { @@ -229,15 +191,8 @@ export class AuthController { }) @ApiBearerAuth() @ApiCookieAuth() - @ApiResponse({ - status: 200, - description: 'Logout successful', - type: LogoutResponseDto, - }) - @ApiResponse({ - status: 401, - description: 'User not authenticated', - }) + @ApiSuccessResponse(200, 'Logout successful', LogoutResponseDto) + @ApiErrorResponse(401, 'User not authenticated') async logout(@Res({ passthrough: true }) res: Response): Promise { // Clear the JWT token using the helper function clearToken(res); diff --git a/src/modules/auth/services/auth.service.ts b/src/modules/auth/services/auth.service.ts index 7ff19d0..6e4fb5b 100644 --- a/src/modules/auth/services/auth.service.ts +++ b/src/modules/auth/services/auth.service.ts @@ -310,7 +310,7 @@ export class AuthService { return user; } - async authenticateUser(walletAddress: string): Promise<{ access_token: string }> { + async authenticateUser(walletAddress: string): Promise<{ token: string }> { // Try to find an existing user with this wallet address let user = await this.userRepository.findOne({ where: { walletAddress }, @@ -350,7 +350,7 @@ export class AuthService { // Create a JWT token containing user information const payload = { sub: user.id, walletAddress: user.walletAddress, role: primaryRole }; - return { access_token: this.jwtService.sign(payload) }; + return { token: this.jwtService.sign(payload) }; } async assignRole(walletAddress: string, roleName: RoleName): Promise { diff --git a/src/modules/coupons/controllers/coupon.controller.ts b/src/modules/coupons/controllers/coupon.controller.ts index f7123ef..cae7341 100644 --- a/src/modules/coupons/controllers/coupon.controller.ts +++ b/src/modules/coupons/controllers/coupon.controller.ts @@ -10,7 +10,8 @@ import { BadRequestException, NotFoundException, } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../../common/decorators/api-response.decorator'; import { CouponService } from '../services/coupon.service'; import { CreateCouponDto, ApplyCouponDto } from '../dto/coupon.dto'; import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard'; @@ -28,7 +29,7 @@ export class CouponController { @UseGuards(JwtAuthGuard, RolesGuard) @Roles('admin') @ApiOperation({ summary: 'Create a new coupon' }) - @ApiResponse({ status: 201, description: 'Coupon created successfully', type: Coupon }) + @ApiSuccessResponse(201, 'Coupon created successfully', Coupon) async createCoupon(@Body() createCouponDto: CreateCouponDto): Promise { try { const coupon = await this.couponService.createCoupon(createCouponDto); @@ -62,8 +63,8 @@ export class CouponController { @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Validate a coupon' }) - @ApiResponse({ status: 200, description: 'Coupon is valid', type: Coupon }) - @ApiResponse({ status: 404, description: 'Coupon not found' }) + @ApiSuccessResponse(200, 'Coupon is valid', Coupon) + @ApiErrorResponse(404, 'Coupon not found') async validateCoupon( @Param('code') code: string, @Body('cartValue') cartValue: number @@ -83,8 +84,8 @@ export class CouponController { @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) @ApiOperation({ summary: 'Apply a coupon to an order' }) - @ApiResponse({ status: 200, description: 'Coupon applied successfully' }) - @ApiResponse({ status: 400, description: 'Invalid coupon' }) + @ApiSuccessResponse(200, 'Coupon applied successfully') + @ApiErrorResponse(400, 'Invalid coupon') async applyCouponToOrder( @Param('code') code: string, @Body() applyCouponDto: ApplyCouponDto diff --git a/src/modules/escrow/controllers/escrow.controller.ts b/src/modules/escrow/controllers/escrow.controller.ts index d11d7b6..7883d5b 100644 --- a/src/modules/escrow/controllers/escrow.controller.ts +++ b/src/modules/escrow/controllers/escrow.controller.ts @@ -9,14 +9,8 @@ import { HttpCode, HttpStatus, } from '@nestjs/common'; -import { - ApiTags, - ApiOperation, - ApiResponse, - ApiBearerAuth, - ApiParam, - ApiBody, -} from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiParam, ApiBody } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../../common/decorators/api-response.decorator'; import { EscrowService } from '../services/escrow.service'; import { ReleaseFundsDto } from '../dto/release-funds.dto'; import { ApproveMilestoneDto } from '../dto/approve-milestone.dto'; @@ -38,23 +32,10 @@ export class EscrowController { description: 'Release funds to seller after buyer approval. Only sellers can release funds for their milestones.', }) @ApiBody({ type: ReleaseFundsDto }) - @ApiResponse({ - status: 200, - description: 'Funds released successfully', - type: ReleaseFundsResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Bad request - milestone not approved, already released, or other validation error', - }) - @ApiResponse({ - status: 403, - description: 'Forbidden - only seller can release funds', - }) - @ApiResponse({ - status: 404, - description: 'Milestone not found', - }) + @ApiSuccessResponse(200, 'Funds released successfully', ReleaseFundsResponseDto) + @ApiErrorResponse(400, 'Bad request - milestone not approved, already released, or other validation error') + @ApiErrorResponse(403, 'Forbidden - only seller can release funds') + @ApiErrorResponse(404, 'Milestone not found') async releaseFunds( @Body() releaseFundsDto: ReleaseFundsDto, @Request() req: AuthenticatedRequest, @@ -69,22 +50,10 @@ export class EscrowController { description: 'Buyer approves or rejects a milestone. Required before funds can be released.', }) @ApiBody({ type: ApproveMilestoneDto }) - @ApiResponse({ - status: 200, - description: 'Milestone approval status updated successfully', - }) - @ApiResponse({ - status: 400, - description: 'Bad request - milestone cannot be approved in current state', - }) - @ApiResponse({ - status: 403, - description: 'Forbidden - only buyer can approve milestones', - }) - @ApiResponse({ - status: 404, - description: 'Milestone not found', - }) + @ApiSuccessResponse(200, 'Milestone approval status updated successfully') + @ApiErrorResponse(400, 'Bad request - milestone cannot be approved in current state') + @ApiErrorResponse(403, 'Forbidden - only buyer can approve milestones') + @ApiErrorResponse(404, 'Milestone not found') async approveMilestone( @Body() approveMilestoneDto: ApproveMilestoneDto, @Request() req: AuthenticatedRequest, @@ -103,19 +72,9 @@ export class EscrowController { type: 'string', format: 'uuid', }) - @ApiResponse({ - status: 200, - description: 'Escrow account retrieved successfully', - type: EscrowAccountDto, - }) - @ApiResponse({ - status: 403, - description: 'Forbidden - only buyer or seller can view escrow account', - }) - @ApiResponse({ - status: 404, - description: 'Escrow account not found', - }) + @ApiSuccessResponse(200, 'Escrow account retrieved successfully', EscrowAccountDto) + @ApiErrorResponse(403, 'Forbidden - only buyer or seller can view escrow account') + @ApiErrorResponse(404, 'Escrow account not found') async getEscrowByOfferId( @Param('offerId') offerId: string, @Request() req: AuthenticatedRequest, @@ -134,19 +93,9 @@ export class EscrowController { type: 'string', format: 'uuid', }) - @ApiResponse({ - status: 200, - description: 'Milestone retrieved successfully', - type: MilestoneDto, - }) - @ApiResponse({ - status: 403, - description: 'Forbidden - only buyer or seller can view milestone', - }) - @ApiResponse({ - status: 404, - description: 'Milestone not found', - }) + @ApiSuccessResponse(200, 'Milestone retrieved successfully', MilestoneDto) + @ApiErrorResponse(403, 'Forbidden - only buyer or seller can view milestone') + @ApiErrorResponse(404, 'Milestone not found') async getMilestoneById( @Param('milestoneId') milestoneId: string, @Request() req: AuthenticatedRequest, diff --git a/src/modules/notifications/controllers/notification.controller.ts b/src/modules/notifications/controllers/notification.controller.ts index 593f3df..dd83d6c 100644 --- a/src/modules/notifications/controllers/notification.controller.ts +++ b/src/modules/notifications/controllers/notification.controller.ts @@ -1,5 +1,6 @@ import { Controller, Post, Body, Request, UseGuards, ForbiddenException } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiSuccessResponse } from '../../../common/decorators/api-response.decorator'; import { NotificationService } from '../services/notification.service'; import { NotificationDto, UserNotificationDto } from '../dto/notification.dto'; import { Role } from '../../../types/role'; @@ -27,7 +28,7 @@ export class NotificationController { @Roles('admin') @ApiBearerAuth() @ApiOperation({ summary: 'Send notification to a user' }) - @ApiResponse({ status: 200, description: 'Notification sent successfully' }) + @ApiSuccessResponse(200, 'Notification sent successfully') async sendToUser(@Body() data: UserNotificationDto, @Request() req: AuthenticatedRequest) { if (!this.isAdmin(req)) { throw new ForbiddenException('Only admins can send notifications'); @@ -44,7 +45,7 @@ export class NotificationController { @Roles('admin') @ApiBearerAuth() @ApiOperation({ summary: 'Broadcast notification to all users' }) - @ApiResponse({ status: 200, description: 'Notification broadcasted successfully' }) + @ApiSuccessResponse(200, 'Notification broadcasted successfully') async broadcast(@Body() data: NotificationDto, @Request() req: AuthenticatedRequest) { if (!this.isAdmin(req)) { throw new ForbiddenException('Only admins can broadcast notifications'); diff --git a/src/modules/offers/offfer.controller.ts b/src/modules/offers/offfer.controller.ts index 904d847..b2cb143 100644 --- a/src/modules/offers/offfer.controller.ts +++ b/src/modules/offers/offfer.controller.ts @@ -1,5 +1,6 @@ import { Body, Controller, Post, HttpCode, HttpStatus } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../common/decorators/api-response.decorator'; import { OfferService } from './services/offer.service'; import { CreateOfferDto } from './dto/create-offer.dto'; @@ -13,9 +14,9 @@ export class OfferController { @Post() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Submit an offer to a buyer request' }) - @ApiResponse({ status: 201, description: 'Offer submitted successfully', type: Offer }) - @ApiResponse({ status: 400, description: 'Bad request or closed buyer request' }) - @ApiResponse({ status: 404, description: 'Buyer request or product not found' }) + @ApiSuccessResponse(201, 'Offer submitted successfully', Offer) + @ApiErrorResponse(400, 'Bad request or closed buyer request') + @ApiErrorResponse(404, 'Buyer request or product not found') async createOffer(@Body() createOfferDto: CreateOfferDto): Promise { return this.offerService.create(createOfferDto); } diff --git a/src/modules/products/controllers/product.controller.ts b/src/modules/products/controllers/product.controller.ts index cccd979..a0494ed 100644 --- a/src/modules/products/controllers/product.controller.ts +++ b/src/modules/products/controllers/product.controller.ts @@ -10,7 +10,8 @@ import { UseGuards, Request, } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../../common/decorators/api-response.decorator'; import { ProductService } from '../services/product.service'; import { JwtAuthGuard } from '../../shared/guards/jwt-auth.guard'; import { RolesGuard } from '../../shared/guards/roles.guard'; @@ -24,7 +25,7 @@ export class ProductController { @Get() @ApiOperation({ summary: 'Get all products' }) - @ApiResponse({ status: 200, description: 'Products retrieved successfully' }) + @ApiSuccessResponse(200, 'Products retrieved successfully') async getAllProducts( @Query('page') page: string = '1', @Query('limit') limit: string = '10', @@ -45,8 +46,8 @@ export class ProductController { @Get(':id') @ApiOperation({ summary: 'Get product by ID' }) - @ApiResponse({ status: 200, description: 'Product retrieved successfully' }) - @ApiResponse({ status: 404, description: 'Product not found' }) + @ApiSuccessResponse(200, 'Product retrieved successfully') + @ApiErrorResponse(404, 'Product not found') async getProductById(@Param('id') id: string) { const product = await this.productService.getProductById(parseInt(id)); return { success: true, data: product }; @@ -57,7 +58,7 @@ export class ProductController { @Roles('seller', 'admin') @ApiBearerAuth() @ApiOperation({ summary: 'Create a new product' }) - @ApiResponse({ status: 201, description: 'Product created successfully' }) + @ApiSuccessResponse(201, 'Product created successfully') async createProduct(@Body() createProductDto: any, @Request() req: any) { const userId = req.user?.id; if (!userId) { @@ -86,7 +87,7 @@ export class ProductController { @Roles('seller', 'admin') @ApiBearerAuth() @ApiOperation({ summary: 'Update a product' }) - @ApiResponse({ status: 200, description: 'Product updated successfully' }) + @ApiSuccessResponse(200, 'Product updated successfully') async updateProduct(@Param('id') id: string, @Body() updateProductDto: any, @Request() req: any) { const userId = req.user?.id; if (!userId) { @@ -116,7 +117,7 @@ export class ProductController { @Roles('seller', 'admin') @ApiBearerAuth() @ApiOperation({ summary: 'Delete a product' }) - @ApiResponse({ status: 200, description: 'Product deleted successfully' }) + @ApiSuccessResponse(200, 'Product deleted successfully') async deleteProduct(@Param('id') id: string, @Request() req: any) { const userId = req.user?.id; if (!userId) { diff --git a/src/modules/seller/controllers/seller.controller.ts b/src/modules/seller/controllers/seller.controller.ts index 751b02a..4b70640 100644 --- a/src/modules/seller/controllers/seller.controller.ts +++ b/src/modules/seller/controllers/seller.controller.ts @@ -8,7 +8,8 @@ import { HttpStatus, Get, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../../common/decorators/api-response.decorator'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../auth/guards/roles.guard'; import { Roles } from '../../auth/decorators/roles.decorator'; @@ -31,19 +32,9 @@ export class SellerController { summary: 'Build unsigned XDR for seller registration', description: 'Creates an unsigned XDR transaction for registering seller on Soroban blockchain', }) - @ApiResponse({ - status: 200, - description: 'Unsigned XDR built successfully', - type: BuildRegisterResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Invalid request or user not eligible', - }) - @ApiResponse({ - status: 409, - description: 'User already has a payout wallet registered', - }) + @ApiSuccessResponse(200, 'Unsigned XDR built successfully', BuildRegisterResponseDto) + @ApiErrorResponse(400, 'Invalid request or user not eligible') + @ApiErrorResponse(409, 'User already has a payout wallet registered') async buildRegister( @Body() buildRegisterDto: BuildRegisterDto, @Request() req: AuthenticatedRequest, @@ -63,15 +54,8 @@ export class SellerController { summary: 'Submit signed XDR for seller registration', description: 'Submits signed XDR to Soroban network and updates user registration status', }) - @ApiResponse({ - status: 200, - description: 'Registration completed successfully', - type: SubmitRegisterResponseDto, - }) - @ApiResponse({ - status: 400, - description: 'Invalid signed XDR or user not eligible', - }) + @ApiSuccessResponse(200, 'Registration completed successfully', SubmitRegisterResponseDto) + @ApiErrorResponse(400, 'Invalid signed XDR or user not eligible') async submitRegister( @Body() submitRegisterDto: SubmitRegisterDto, @Request() req: AuthenticatedRequest, @@ -91,10 +75,7 @@ export class SellerController { summary: 'Get seller registration status', description: 'Returns the current registration status of the seller', }) - @ApiResponse({ - status: 200, - description: 'Registration status retrieved successfully', - }) + @ApiSuccessResponse(200, 'Registration status retrieved successfully') async getRegistrationStatus(@Request() req: AuthenticatedRequest) { const result = await this.sellerService.getRegistrationStatus(Number(req.user.id)); diff --git a/src/modules/seller/tests/seller.e2e.spec.ts b/src/modules/seller/tests/seller.e2e.spec.ts index 0c1c119..a421af2 100644 --- a/src/modules/seller/tests/seller.e2e.spec.ts +++ b/src/modules/seller/tests/seller.e2e.spec.ts @@ -8,6 +8,7 @@ import { AuthModule } from '../../auth/auth.module'; import { User } from '../../users/entities/user.entity'; import { Role } from '../../auth/entities/role.entity'; import { UserRole } from '../../auth/entities/user-role.entity'; +import { ResponseInterceptor } from '../../../common/interceptors/response.interceptor'; describe('Seller (e2e)', () => { let app: INestApplication; @@ -34,23 +35,27 @@ describe('Seller (e2e)', () => { }).compile(); app = moduleFixture.createNestApplication(); + // Align with production: apply global prefix and ResponseInterceptor + app.setGlobalPrefix('api/v1'); + app.useGlobalInterceptors(new ResponseInterceptor()); await app.init(); // Create test user and get auth token const authResponse = await request(app.getHttpServer()) - .post('/auth/login') + .post('/api/v1/auth/login') .send({ walletAddress: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', }); - authToken = authResponse.body.data.token; - sellerId = authResponse.body.data.user.id; + // Token is injected at top-level by ResponseInterceptor + authToken = authResponse.body.token; + const walletAddress = authResponse.body.data.user.walletAddress; // Assign seller role to user await request(app.getHttpServer()) - .patch(`/users/${sellerId}/role`) + .post(`/api/v1/roles/assign`) .set('Authorization', `Bearer ${authToken}`) - .send({ role: 'seller' }); + .send({ walletAddress, roleName: 'seller' }); }); afterAll(async () => { @@ -60,7 +65,7 @@ describe('Seller (e2e)', () => { describe('POST /seller/contract/build-register', () => { it('should build unsigned XDR successfully', async () => { const response = await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .set('Authorization', `Bearer ${authToken}`) .send({ payoutWallet: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY', @@ -76,7 +81,7 @@ describe('Seller (e2e)', () => { it('should return 400 for invalid payout wallet format', async () => { await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .set('Authorization', `Bearer ${authToken}`) .send({ payoutWallet: 'invalid-wallet', @@ -86,7 +91,7 @@ describe('Seller (e2e)', () => { it('should return 401 without authentication', async () => { await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .send({ payoutWallet: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY', }) @@ -96,7 +101,7 @@ describe('Seller (e2e)', () => { it('should return 409 when trying to register again', async () => { // First registration await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .set('Authorization', `Bearer ${authToken}`) .send({ payoutWallet: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY', @@ -105,7 +110,7 @@ describe('Seller (e2e)', () => { // Submit the registration await request(app.getHttpServer()) - .post('/seller/contract/submit') + .post('/api/v1/seller/contract/submit') .set('Authorization', `Bearer ${authToken}`) .send({ signedXdr: 'AAAAAgAAAABqjgAAAAAA...', @@ -114,7 +119,7 @@ describe('Seller (e2e)', () => { // Try to register again - should fail await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .set('Authorization', `Bearer ${authToken}`) .send({ payoutWallet: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY', @@ -127,7 +132,7 @@ describe('Seller (e2e)', () => { it('should submit signed XDR and update DB successfully', async () => { // First build the registration await request(app.getHttpServer()) - .post('/seller/contract/build-register') + .post('/api/v1/seller/contract/build-register') .set('Authorization', `Bearer ${authToken}`) .send({ payoutWallet: 'GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXY', @@ -136,7 +141,7 @@ describe('Seller (e2e)', () => { // Then submit const response = await request(app.getHttpServer()) - .post('/seller/contract/submit') + .post('/api/v1/seller/contract/submit') .set('Authorization', `Bearer ${authToken}`) .send({ signedXdr: 'AAAAAgAAAABqjgAAAAAA...', @@ -152,7 +157,7 @@ describe('Seller (e2e)', () => { it('should return 400 for missing signature', async () => { await request(app.getHttpServer()) - .post('/seller/contract/submit') + .post('/api/v1/seller/contract/submit') .set('Authorization', `Bearer ${authToken}`) .send({ signedXdr: '', @@ -162,7 +167,7 @@ describe('Seller (e2e)', () => { it('should return 400 for invalid signed XDR', async () => { await request(app.getHttpServer()) - .post('/seller/contract/submit') + .post('/api/v1/seller/contract/submit') .set('Authorization', `Bearer ${authToken}`) .send({ signedXdr: 'invalid', @@ -172,7 +177,7 @@ describe('Seller (e2e)', () => { it('should return 401 without authentication', async () => { await request(app.getHttpServer()) - .post('/seller/contract/submit') + .post('/api/v1/seller/contract/submit') .send({ signedXdr: 'AAAAAgAAAABqjgAAAAAA...', }) @@ -183,7 +188,7 @@ describe('Seller (e2e)', () => { describe('GET /seller/contract/status', () => { it('should return registration status', async () => { const response = await request(app.getHttpServer()) - .get('/seller/contract/status') + .get('/api/v1/seller/contract/status') .set('Authorization', `Bearer ${authToken}`) .expect(200); @@ -194,7 +199,7 @@ describe('Seller (e2e)', () => { it('should return 401 without authentication', async () => { await request(app.getHttpServer()) - .get('/seller/contract/status') + .get('/api/v1/seller/contract/status') .expect(401); }); }); diff --git a/src/modules/stores/controllers/store.controller.ts b/src/modules/stores/controllers/store.controller.ts index a3325b9..4bd2d5d 100644 --- a/src/modules/stores/controllers/store.controller.ts +++ b/src/modules/stores/controllers/store.controller.ts @@ -13,7 +13,8 @@ import { HttpStatus, ParseIntPipe, } from '@nestjs/common'; -import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse } from '../../../common/decorators/api-response.decorator'; import { StoreService } from '../services/store.service'; import { CreateStoreDto, UpdateStoreDto, StoreResponseDto } from '../dto/store.dto'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; @@ -32,11 +33,7 @@ export class StoreController { @Roles(Role.SELLER) @ApiBearerAuth() @ApiOperation({ summary: 'Create a new store' }) - @ApiResponse({ - status: 201, - description: 'Store created successfully', - type: StoreResponseDto, - }) + @ApiSuccessResponse(201, 'Store created successfully', StoreResponseDto) @HttpCode(HttpStatus.CREATED) async createStore( @Body() createStoreDto: CreateStoreDto, @@ -56,11 +53,7 @@ export class StoreController { @Roles(Role.SELLER) @ApiBearerAuth() @ApiOperation({ summary: 'Get all stores for the authenticated seller' }) - @ApiResponse({ - status: 200, - description: 'Stores retrieved successfully', - type: [StoreResponseDto], - }) + @ApiSuccessResponse(200, 'Stores retrieved successfully', StoreResponseDto, true) async getMyStores( @Req() req: AuthenticatedRequest, ): Promise<{ success: boolean; data: StoreResponseDto[] }> { @@ -75,11 +68,7 @@ export class StoreController { @Get(':id') @ApiOperation({ summary: 'Get a specific store by ID' }) - @ApiResponse({ - status: 200, - description: 'Store retrieved successfully', - type: StoreResponseDto, - }) + @ApiSuccessResponse(200, 'Store retrieved successfully', StoreResponseDto) async getStoreById( @Param('id', ParseIntPipe) id: number, ): Promise<{ success: boolean; data: StoreResponseDto }> { @@ -96,11 +85,7 @@ export class StoreController { @Roles(Role.SELLER) @ApiBearerAuth() @ApiOperation({ summary: 'Update a store' }) - @ApiResponse({ - status: 200, - description: 'Store updated successfully', - type: StoreResponseDto, - }) + @ApiSuccessResponse(200, 'Store updated successfully', StoreResponseDto) async updateStore( @Param('id', ParseIntPipe) id: number, @Body() updateStoreDto: UpdateStoreDto, @@ -120,10 +105,7 @@ export class StoreController { @Roles(Role.SELLER) @ApiBearerAuth() @ApiOperation({ summary: 'Delete a store' }) - @ApiResponse({ - status: 200, - description: 'Store deleted successfully', - }) + @ApiSuccessResponse(200, 'Store deleted successfully') @HttpCode(HttpStatus.OK) async deleteStore( @Param('id', ParseIntPipe) id: number, @@ -140,11 +122,7 @@ export class StoreController { @Get() @ApiOperation({ summary: 'Get all active stores' }) - @ApiResponse({ - status: 200, - description: 'Stores retrieved successfully', - type: [StoreResponseDto], - }) + @ApiSuccessResponse(200, 'Stores retrieved successfully', StoreResponseDto, true) async getActiveStores(): Promise<{ success: boolean; data: StoreResponseDto[] }> { const stores = await this.storeService.getActiveStores(); @@ -156,11 +134,7 @@ export class StoreController { @Get('search') @ApiOperation({ summary: 'Search stores' }) - @ApiResponse({ - status: 200, - description: 'Stores retrieved successfully', - type: [StoreResponseDto], - }) + @ApiSuccessResponse(200, 'Stores retrieved successfully', StoreResponseDto, true) async searchStores( @Query('q') query?: string, @Query('category') category?: string, @@ -179,10 +153,7 @@ export class StoreController { @Roles(Role.SELLER) @ApiBearerAuth() @ApiOperation({ summary: 'Get store statistics' }) - @ApiResponse({ - status: 200, - description: 'Store statistics retrieved successfully', - }) + @ApiSuccessResponse(200, 'Store statistics retrieved successfully') async getStoreStats( @Param('id', ParseIntPipe) id: number, @Req() req: AuthenticatedRequest, @@ -202,11 +173,7 @@ export class StoreController { @Roles(Role.ADMIN) @ApiBearerAuth() @ApiOperation({ summary: 'Update store status (admin only)' }) - @ApiResponse({ - status: 200, - description: 'Store status updated successfully', - type: StoreResponseDto, - }) + @ApiSuccessResponse(200, 'Store status updated successfully', StoreResponseDto) async updateStoreStatus( @Param('id', ParseIntPipe) id: number, @Body('status') status: string, diff --git a/src/modules/users/controllers/user.controller.ts b/src/modules/users/controllers/user.controller.ts index e82beea..84e2455 100644 --- a/src/modules/users/controllers/user.controller.ts +++ b/src/modules/users/controllers/user.controller.ts @@ -20,6 +20,9 @@ import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../auth/guards/roles.guard'; import { Roles } from '../../auth/decorators/roles.decorator'; import { Role } from '../../../types/role'; +import { ApiTags, ApiOperation, ApiBearerAuth, ApiCookieAuth } from '@nestjs/swagger'; +import { ApiSuccessResponse, ApiErrorResponse, ApiAuthResponse } from '../../../common/decorators/api-response.decorator'; +import { setToken } from '../../../common/utils/response.utils'; interface UserResponse { walletAddress: string; @@ -49,6 +52,7 @@ interface UsersListResponse { data: UserResponse[]; } +@ApiTags('Users') @Controller('users') export class UserController { constructor( @@ -62,6 +66,8 @@ export class UserController { */ @Post() @HttpCode(HttpStatus.CREATED) + @ApiOperation({ summary: 'Register a new user with wallet' }) + @ApiAuthResponse(HttpStatus.CREATED, 'User registered successfully', Object as any) async createUser( @Body() registerDto: RegisterUserDto, @@ -78,13 +84,8 @@ export class UserController { sellerData: registerDto.sellerData, }); - // Set JWT token in HttpOnly cookie - res.cookie('token', result.token, { - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - maxAge: result.expiresIn * 1000, - }); + // Set JWT token using helper to align with ResponseInterceptor + setToken(res, result.token, { maxAge: result.expiresIn * 1000 }); return { success: true, @@ -111,6 +112,11 @@ export class UserController { @Put('update/:walletAddress') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) + @ApiBearerAuth() + @ApiCookieAuth() + @ApiOperation({ summary: 'Update user information' }) + @ApiSuccessResponse(HttpStatus.OK, 'User updated successfully', Object as any) + @ApiErrorResponse(HttpStatus.UNAUTHORIZED, 'You can only update your own profile') async updateUser( @Param('walletAddress') walletAddress: string, @Body() updateDto: UpdateUserDto, @@ -149,6 +155,12 @@ export class UserController { @Get(':walletAddress') @UseGuards(JwtAuthGuard) @HttpCode(HttpStatus.OK) + @ApiBearerAuth() + @ApiCookieAuth() + @ApiOperation({ summary: 'Get user by wallet address' }) + @ApiSuccessResponse(HttpStatus.OK, 'User retrieved successfully', Object as any) + @ApiErrorResponse(HttpStatus.UNAUTHORIZED, 'Access denied') + @ApiErrorResponse(HttpStatus.NOT_FOUND, 'User not found') async getUserByWalletAddress( @Param('walletAddress') walletAddress: string, @Req() req: Request @@ -188,6 +200,11 @@ export class UserController { @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.ADMIN) @HttpCode(HttpStatus.OK) + @ApiBearerAuth() + @ApiCookieAuth() + @ApiOperation({ summary: 'Get all users (admin only)' }) + @ApiSuccessResponse(HttpStatus.OK, 'Users retrieved successfully', Object as any, true) + @ApiErrorResponse(HttpStatus.FORBIDDEN, 'Forbidden - Admin access required') async getAllUsers(): Promise { const users = await this.userService.getUsers(); diff --git a/test/auth.e2e-spec.ts b/test/auth.e2e-spec.ts index b812efc..396aa3e 100644 --- a/test/auth.e2e-spec.ts +++ b/test/auth.e2e-spec.ts @@ -3,6 +3,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import { Keypair } from 'stellar-sdk'; import { AppModule } from '../src/app.module'; +import { ResponseInterceptor } from '../src/common/interceptors/response.interceptor'; describe('Auth Endpoints (e2e)', () => { let app: INestApplication; @@ -16,6 +17,9 @@ describe('Auth Endpoints (e2e)', () => { }).compile(); app = moduleFixture.createNestApplication(); + // Apply same global settings as production + app.setGlobalPrefix('api/v1'); + app.useGlobalInterceptors(new ResponseInterceptor()); await app.init(); // Generate a mock signature for testing From dd57769abc700acda1a4335a5a2a93a59ebbeed8 Mon Sep 17 00:00:00 2001 From: Big-cedar Date: Fri, 3 Oct 2025 23:53:10 +0100 Subject: [PATCH 2/2] chpres: fix coderabbit comments --- .../auth/controllers/auth.controller.ts | 38 +++++++++++-------- src/modules/auth/dto/auth-response.dto.ts | 7 ++++ ...ffer.controller.ts => offer.controller.ts} | 0 src/modules/offers/offer.module.ts | 2 +- 4 files changed, 30 insertions(+), 17 deletions(-) rename src/modules/offers/{offfer.controller.ts => offer.controller.ts} (100%) diff --git a/src/modules/auth/controllers/auth.controller.ts b/src/modules/auth/controllers/auth.controller.ts index d571c8f..7e91eaa 100644 --- a/src/modules/auth/controllers/auth.controller.ts +++ b/src/modules/auth/controllers/auth.controller.ts @@ -85,16 +85,19 @@ export class AuthController { maxAge: result.expiresIn * 1000, // Convert to milliseconds }); - // Return plain data; ResponseInterceptor will wrap and include token + // Return standardized data; ResponseInterceptor will include token return { - user: { - walletAddress: result.user.walletAddress, - name: result.user.name, - email: result.user.email, - role: result.user.userRoles?.[0]?.role?.name || 'buyer', + success: true, + data: { + user: { + walletAddress: result.user.walletAddress, + name: result.user.name, + email: result.user.email, + role: result.user.userRoles?.[0]?.role?.name || 'buyer', + }, + expiresIn: result.expiresIn, }, - expiresIn: result.expiresIn, - } as any; + }; } /** @@ -129,16 +132,19 @@ export class AuthController { maxAge: result.expiresIn * 1000, // Convert to milliseconds }); - // Return plain data; ResponseInterceptor will wrap and include token + // Return standardized data; ResponseInterceptor will include token return { - user: { - walletAddress: result.user.walletAddress, - name: result.user.name, - email: result.user.email, - role: result.user.userRoles?.[0]?.role?.name || 'buyer', + success: true, + data: { + user: { + walletAddress: result.user.walletAddress, + name: result.user.name, + email: result.user.email, + role: result.user.userRoles?.[0]?.role?.name || 'buyer', + }, + expiresIn: result.expiresIn, }, - expiresIn: result.expiresIn, - } as any; + }; } /** diff --git a/src/modules/auth/dto/auth-response.dto.ts b/src/modules/auth/dto/auth-response.dto.ts index dc9c68a..fe5dede 100644 --- a/src/modules/auth/dto/auth-response.dto.ts +++ b/src/modules/auth/dto/auth-response.dto.ts @@ -55,6 +55,13 @@ export class AuthResponseDto { }) success: boolean; + @ApiProperty({ + description: 'JWT token for authentication', + example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', + required: false, + }) + token?: string; + @ApiProperty({ description: 'Authentication data', example: { diff --git a/src/modules/offers/offfer.controller.ts b/src/modules/offers/offer.controller.ts similarity index 100% rename from src/modules/offers/offfer.controller.ts rename to src/modules/offers/offer.controller.ts diff --git a/src/modules/offers/offer.module.ts b/src/modules/offers/offer.module.ts index 18793c0..92aee03 100644 --- a/src/modules/offers/offer.module.ts +++ b/src/modules/offers/offer.module.ts @@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Offer } from './entities/offer.entity'; import { OfferService } from './services/offer.service'; -import { OfferController } from './offfer.controller'; +import { OfferController } from './offer.controller'; import { BuyerRequest } from '../buyer-requests/entities/buyer-request.entity'; import { Notification } from '../notifications/entities/notification.entity'; import { Product } from '../products/entities/product.entity';