diff --git a/src/modules/accounts/accounts.controller.ts b/src/modules/accounts/accounts.controller.ts index 03d8194..014a468 100644 --- a/src/modules/accounts/accounts.controller.ts +++ b/src/modules/accounts/accounts.controller.ts @@ -12,28 +12,43 @@ import { ApiOperation, ApiResponse, ApiBearerAuth, + ApiQuery, } from '@nestjs/swagger'; import { ThrottlerGuard } from '@nestjs/throttler'; import { AccountsService } from './accounts.service.js'; import { CreateAccountDto } from './dto/create-account.dto.js'; import { AccountResponseDto } from './dto/account-response.dto.js'; +import { AccountsListResponseDto } from './dto/accounts-list-response.dto.js'; import { AccountStatus } from './entities/account.entity.js'; @ApiTags('accounts') +@ApiBearerAuth() @Controller('accounts') @UseGuards(ThrottlerGuard) export class AccountsController { constructor(private readonly accountsService: AccountsService) {} @Post() - @ApiOperation({ summary: 'Create ephemeral account' }) + @ApiOperation({ + summary: 'Create ephemeral escrow account', + description: + 'Creates a temporary Stellar escrow account for fund holding. ' + + 'This is NOT a user wallet — it is an ephemeral account that will be ' + + 'swept to a destination address upon claim or expire after the set duration. ' + + 'Returns claim URL for the recipient to access funds.', + }) @ApiResponse({ status: 201, - description: 'Account created', + description: 'Ephemeral account created successfully', type: AccountResponseDto, }) - @ApiResponse({ status: 400, description: 'Invalid input' }) - @ApiBearerAuth() + @ApiResponse({ status: 400, description: 'Invalid input parameters' }) + @ApiResponse({ status: 401, description: 'Authentication required' }) + @ApiResponse({ + status: 429, + description: + 'Rate limit exceeded — requests are throttled to protect funding flows', + }) public async create( @Body() createAccountDto: CreateAccountDto, ): Promise { @@ -41,25 +56,81 @@ export class AccountsController { } @Get(':id') - @ApiOperation({ summary: 'Get account by ID' }) + @ApiOperation({ + summary: 'Get ephemeral account lifecycle state', + description: + 'Retrieves the current state of an ephemeral account including its ' + + 'status (pending_payment, pending_claim, claimed, expired, failed), ' + + 'funding details, and claim information.', + }) @ApiResponse({ status: 200, - description: 'Account details', + description: 'Account details retrieved successfully', type: AccountResponseDto, }) + @ApiResponse({ status: 401, description: 'Authentication required' }) @ApiResponse({ status: 404, description: 'Account not found' }) + @ApiResponse({ + status: 429, + description: + 'Rate limit exceeded — requests are throttled to protect funding flows', + }) public async findOne(@Param('id') id: string): Promise { return this.accountsService.findOne(id); } @Get() - @ApiOperation({ summary: 'List accounts' }) - @ApiResponse({ status: 200, description: 'List of accounts' }) + @ApiOperation({ + summary: 'List ephemeral accounts (Admin)', + description: + 'Administrative endpoint for listing all ephemeral accounts with optional filtering. ' + + 'Supports filtering by status and cursor-based pagination using limit/offset. ' + + 'Maximum 100 records per request.', + }) + @ApiQuery({ + name: 'status', + enum: AccountStatus, + required: false, + description: + 'Filter accounts by lifecycle status:\n' + + '- `pending_payment`: Account created, awaiting funding\n' + + '- `pending_claim`: Funded and ready for recipient claim\n' + + '- `claimed`: Funds successfully transferred to recipient\n' + + '- `expired`: Claim window closed, funds swept back\n' + + '- `failed`: Error during creation or funding', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Maximum number of records to return (default: 50, max: 100)', + example: 50, + }) + @ApiQuery({ + name: 'offset', + type: Number, + required: false, + description: + 'Number of records to skip for cursor-based pagination. ' + + 'Use with limit to paginate through results. Not page-based.', + example: 0, + }) + @ApiResponse({ + status: 200, + description: 'List of accounts retrieved successfully', + type: AccountsListResponseDto, + }) + @ApiResponse({ status: 401, description: 'Authentication required' }) + @ApiResponse({ + status: 429, + description: + 'Rate limit exceeded — requests are throttled to protect funding flows', + }) public async findAll( @Query('status') status?: AccountStatus, @Query('limit') limit = 50, @Query('offset') offset = 0, - ) { + ): Promise { return this.accountsService.findAll({ status, limit, offset }); } } diff --git a/src/modules/accounts/dto/account-response.dto.ts b/src/modules/accounts/dto/account-response.dto.ts index 2f55d0c..64ec2ca 100644 --- a/src/modules/accounts/dto/account-response.dto.ts +++ b/src/modules/accounts/dto/account-response.dto.ts @@ -1,18 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { AccountStatus } from '../entities/account.entity.js'; -/** - * Response DTO for account details - * - * Represents the public-facing contract for ephemeral Stellar account information. - * This DTO is used in both account creation responses and account lookup endpoints. - * - * @remarks - * - All fields are readonly to ensure immutability in API responses - * - Date fields are in ISO 8601 format - * - Amounts are represented as string decimals to preserve precision - * - Status enum values indicate the current lifecycle state of the account - */ export class AccountResponseDto { @ApiProperty({ description: @@ -64,8 +52,7 @@ export class AccountResponseDto { amount: string; @ApiProperty({ - description: - 'Asset code for the funds in this account (e.g., XLM, USDC)', + description: 'Asset code for the funds in this account (e.g., XLM, USDC)', example: 'XLM', maxLength: 100, readOnly: true, diff --git a/src/modules/accounts/dto/accounts-list-response.dto.ts b/src/modules/accounts/dto/accounts-list-response.dto.ts new file mode 100644 index 0000000..db64265 --- /dev/null +++ b/src/modules/accounts/dto/accounts-list-response.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { AccountResponseDto } from './account-response.dto.js'; + +export class AccountsListResponseDto { + @ApiProperty({ + description: 'Array of ephemeral account records', + type: [AccountResponseDto], + }) + accounts: AccountResponseDto[]; + + @ApiProperty({ + description: 'Total number of accounts matching the query (for pagination)', + example: 150, + }) + total: number; +}