import { Controller, Get, Post, Patch, Delete, Body, Param, ParseIntPipe } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiOkResponse, ApiCreatedResponse, ApiExtraModels, getSchemaPath, } from '@nestjs/swagger'; import { PortfolioService } from './portfolio.service'; import { CreatePortfolioDto } from './dto/create-portfolio.dto'; import { UpdatePortfolioDto } from './dto/update-portfolio.dto'; import { AddPositionDto } from './dto/add-position.dto'; import { UpdatePositionDto } from './dto/update-position.dto'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { AnalyticsEnvelopeDto, PortfolioDetailEnvelopeDto, PortfolioEnvelopeDto, PortfolioListEnvelopeDto, PortfolioResponseMetaDto, PositionEnvelopeDto, } from './dto/portfolio-envelope.dto'; const nullDataEnvelopeSchema = { type: 'object', properties: { data: { type: 'null', }, meta: { $ref: getSchemaPath(PortfolioResponseMetaDto), }, }, required: ['data', 'meta'], }; @ApiTags('Portfolios') @ApiBearerAuth() @ApiExtraModels(PortfolioResponseMetaDto) @Controller('portfolios') export class PortfolioController { constructor(private readonly portfolioService: PortfolioService) {} @Get() @ApiOperation({ summary: 'Get all portfolios for current user' }) @ApiOkResponse({ type: PortfolioListEnvelopeDto }) async findAll(@CurrentUser() user: { sub: number }) { const portfolios = await this.portfolioService.findAll(user.sub); return { data: portfolios, meta: { cachedAt: null, fromCache: false } }; } @Post() @ApiOperation({ summary: 'Create a new portfolio' }) @ApiCreatedResponse({ type: PortfolioEnvelopeDto }) async create(@CurrentUser() user: { sub: number }, @Body() dto: CreatePortfolioDto) { const portfolio = await this.portfolioService.create(user.sub, dto); return { data: portfolio, meta: { cachedAt: null, fromCache: false } }; } @Get(':id') @ApiOperation({ summary: 'Get portfolio details with positions and prices' }) @ApiOkResponse({ type: PortfolioDetailEnvelopeDto }) async findOne(@CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number) { const portfolio = await this.portfolioService.findOne(user.sub, id); return { data: portfolio, meta: { cachedAt: null, fromCache: false } }; } @Patch(':id') @ApiOperation({ summary: 'Update portfolio' }) @ApiOkResponse({ type: PortfolioEnvelopeDto }) async update( @CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number, @Body() dto: UpdatePortfolioDto, ) { const portfolio = await this.portfolioService.update(user.sub, id, dto); return { data: portfolio, meta: { cachedAt: null, fromCache: false } }; } @Delete(':id') @ApiOperation({ summary: 'Delete portfolio' }) @ApiOkResponse({ schema: nullDataEnvelopeSchema }) async remove(@CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number) { await this.portfolioService.remove(user.sub, id); return { data: null, meta: { cachedAt: null, fromCache: false } }; } @Post(':id/positions') @ApiOperation({ summary: 'Add position to portfolio' }) @ApiCreatedResponse({ type: PositionEnvelopeDto }) async addPosition( @CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number, @Body() dto: AddPositionDto, ) { const position = await this.portfolioService.addPosition(user.sub, id, dto); return { data: position, meta: { cachedAt: null, fromCache: false } }; } @Patch(':id/positions/:positionId') @ApiOperation({ summary: 'Update position' }) @ApiOkResponse({ type: PositionEnvelopeDto }) async updatePosition( @CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number, @Param('positionId', ParseIntPipe) positionId: number, @Body() dto: UpdatePositionDto, ) { const position = await this.portfolioService.updatePosition(user.sub, id, positionId, dto); return { data: position, meta: { cachedAt: null, fromCache: false } }; } @Get(':id/analytics') @ApiOperation({ summary: 'Get portfolio analytics with PnL' }) @ApiOkResponse({ type: AnalyticsEnvelopeDto }) async getAnalytics(@CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number) { const result = await this.portfolioService.getAnalytics(user.sub, id); return { data: result, meta: { cachedAt: null, fromCache: false } }; } @Delete(':id/positions/:positionId') @ApiOperation({ summary: 'Remove position from portfolio' }) @ApiOkResponse({ schema: nullDataEnvelopeSchema }) async removePosition( @CurrentUser() user: { sub: number }, @Param('id', ParseIntPipe) id: number, @Param('positionId', ParseIntPipe) positionId: number, ) { await this.portfolioService.removePosition(user.sub, id, positionId); return { data: null, meta: { cachedAt: null, fromCache: false } }; } }