From cc4c8254f61c293dd328f43883719339eeb2aa27 Mon Sep 17 00:00:00 2001 From: Abdalhameed Ahmad Date: Tue, 9 Sep 2025 21:37:55 +0300 Subject: [PATCH] feat: view child active cards --- src/card/controllers/cards.controller.ts | 11 ++++- .../dtos/responses/child-card.response.dto.ts | 48 +++++++++++++++++++ src/card/dtos/responses/index.ts | 1 + src/card/repositories/card.repository.ts | 12 ++++- src/card/services/card.service.ts | 26 +++++++++- .../dtos/response/junior.response.dto.ts | 6 +-- 6 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 src/card/dtos/responses/child-card.response.dto.ts diff --git a/src/card/controllers/cards.controller.ts b/src/card/controllers/cards.controller.ts index 4b1a12a..dd26753 100644 --- a/src/card/controllers/cards.controller.ts +++ b/src/card/controllers/cards.controller.ts @@ -8,7 +8,7 @@ import { CardEmbossingDetailsResponseDto } from '~/common/modules/neoleap/dtos/r import { ApiDataResponse } from '~/core/decorators'; import { ResponseFactory } from '~/core/utils'; import { FundIbanRequestDto } from '../dtos/requests'; -import { AccountIbanResponseDto, CardResponseDto } from '../dtos/responses'; +import { AccountIbanResponseDto, CardResponseDto, ChildCardResponseDto } from '../dtos/responses'; import { CardService } from '../services'; @Controller('cards') @@ -25,6 +25,15 @@ export class CardsController { return ResponseFactory.data(new CardResponseDto(card)); } + @Get('child-cards') + @UseGuards(RolesGuard) + @AllowedRoles(Roles.GUARDIAN) + @ApiDataResponse(ChildCardResponseDto) + async getChildCards(@AuthenticatedUser() { sub }: IJwtPayload) { + const cards = await this.cardService.getChildCards(sub); + return ResponseFactory.data(cards.map((card) => new ChildCardResponseDto(card))); + } + @Get('current') @ApiDataResponse(CardResponseDto) async getCurrentCard(@AuthenticatedUser() { sub }: IJwtPayload) { diff --git a/src/card/dtos/responses/child-card.response.dto.ts b/src/card/dtos/responses/child-card.response.dto.ts new file mode 100644 index 0000000..258ea8a --- /dev/null +++ b/src/card/dtos/responses/child-card.response.dto.ts @@ -0,0 +1,48 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Card } from '~/card/entities'; +import { Gender } from '~/customer/enums'; +import { DocumentMetaResponseDto } from '~/document/dtos/response'; +import { CardResponseDto } from './card.response.dto'; + +class JuniorInfo { + @ApiProperty({ example: 'id' }) + id!: string; + + @ApiProperty({ example: 'FirstName' }) + firstName!: string; + + @ApiProperty({ example: 'LastName' }) + lastName!: string; + + @ApiProperty({ example: 'test@example.com' }) + email!: string; + + @ApiProperty({ enum: Gender, example: Gender.MALE }) + gender!: Gender; + + @ApiProperty({ example: '2000-01-01' }) + dateOfBirth!: Date; + + @ApiProperty({ example: DocumentMetaResponseDto, nullable: true }) + profilePicture!: DocumentMetaResponseDto | null; + + constructor(card: Card) { + this.id = card.customer?.junior?.id; + this.firstName = card.customer?.firstName; + this.lastName = card.customer?.lastName; + this.email = card.customer?.user?.email; + this.gender = card.customer.gender; + this.profilePicture = card.customer?.user?.profilePicture + ? new DocumentMetaResponseDto(card.customer.user.profilePicture) + : null; + } +} +export class ChildCardResponseDto extends CardResponseDto { + @ApiProperty({ type: JuniorInfo }) + junior!: JuniorInfo | null; + + constructor(card: Card) { + super(card); + this.junior = card.customer?.junior ? new JuniorInfo(card) : null; + } +} diff --git a/src/card/dtos/responses/index.ts b/src/card/dtos/responses/index.ts index e32607e..ae3310e 100644 --- a/src/card/dtos/responses/index.ts +++ b/src/card/dtos/responses/index.ts @@ -1,2 +1,3 @@ export * from './account-iban.response.dto'; export * from './card.response.dto'; +export * from './child-card.response.dto'; diff --git a/src/card/repositories/card.repository.ts b/src/card/repositories/card.repository.ts index b8fc8be..ee121a5 100644 --- a/src/card/repositories/card.repository.ts +++ b/src/card/repositories/card.repository.ts @@ -14,14 +14,14 @@ export class CardRepository { accountId: string, card: CreateApplicationResponse, cardColor?: CardColors, - isChildCard = false, + parentId?: string, ): Promise { return this.cardRepository.save( this.cardRepository.create({ customerId: customerId, expiry: card.expiryDate, cardReference: card.cardId, - customerType: isChildCard ? CustomerType.CHILD : CustomerType.PARENT, + customerType: parentId ? CustomerType.CHILD : CustomerType.PARENT, firstSixDigits: card.firstSixDigits, lastFourDigits: card.lastFourDigits, color: cardColor ? cardColor : CardColors.DEEP_MAGENTA, @@ -29,10 +29,18 @@ export class CardRepository { issuer: CardIssuers.NEOLEAP, accountId: accountId, vpan: card.vpan, + parentId, }), ); } + findChildCardsForGuardian(guardianId: string): Promise { + return this.cardRepository.find({ + where: { parentId: guardianId, customerType: CustomerType.CHILD }, + relations: ['account', 'customer', 'customer.user', 'customer.user.profilePicture', 'customer.junior'], + }); + } + getCardById(id: string): Promise { return this.cardRepository.findOne({ where: { id }, relations: ['account'] }); } diff --git a/src/card/services/card.service.ts b/src/card/services/card.service.ts index fb8c8b7..bd5e8ed 100644 --- a/src/card/services/card.service.ts +++ b/src/card/services/card.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, forwardRef, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import Decimal from 'decimal.js'; import { Transactional } from 'typeorm-transactional'; import { AccountCardStatusChangedWebhookRequest } from '~/common/modules/neoleap/dtos/requests'; @@ -6,6 +6,7 @@ import { NeoLeapService } from '~/common/modules/neoleap/services'; import { Customer } from '~/customer/entities'; import { KycStatus } from '~/customer/enums'; import { CustomerService } from '~/customer/services'; +import { OciService } from '~/document/services'; import { Card } from '../entities'; import { CardColors } from '../enums'; import { CardStatusMapper } from '../mappers/card-status.mapper'; @@ -15,9 +16,11 @@ import { TransactionService } from './transaction.service'; @Injectable() export class CardService { + private readonly logger = new Logger(CardService.name); constructor( private readonly cardRepository: CardRepository, private readonly accountService: AccountService, + private readonly ociService: OciService, @Inject(forwardRef(() => TransactionService)) private readonly transactionService: TransactionService, @Inject(forwardRef(() => NeoLeapService)) private readonly neoleapService: NeoLeapService, @Inject(forwardRef(() => CustomerService)) private readonly customerService: CustomerService, @@ -42,6 +45,12 @@ export class CardService { return this.getCardById(createdCard.id); } + async getChildCards(guardianId: string): Promise { + const cards = await this.cardRepository.findChildCardsForGuardian(guardianId); + await this.prepareJuniorImages(cards); + return cards; + } + async createCardForChild(parentCustomer: Customer, childCustomer: Customer, cardColor: CardColors, cardPin: string) { const data = await this.neoleapService.createChildCard(parentCustomer, childCustomer, cardPin); const createdCard = await this.cardRepository.createCard( @@ -49,7 +58,7 @@ export class CardService { parentCustomer.cards[0].account.id, data, cardColor, - true, + parentCustomer.id, ); return this.getCardById(createdCard.id); @@ -140,4 +149,17 @@ export class CardService { fundIban(iban: string, amount: number) { return this.accountService.fundIban(iban, amount); } + + private async prepareJuniorImages(cards: Card[]) { + this.logger.log(`Preparing junior images`); + await Promise.all( + cards.map(async (card) => { + const profilePicture = card.customer?.user?.profilePicture; + + if (profilePicture) { + profilePicture.url = await this.ociService.generatePreSignedUrl(profilePicture); + } + }), + ); + } } diff --git a/src/junior/dtos/response/junior.response.dto.ts b/src/junior/dtos/response/junior.response.dto.ts index 6e21cd0..5c3a0f5 100644 --- a/src/junior/dtos/response/junior.response.dto.ts +++ b/src/junior/dtos/response/junior.response.dto.ts @@ -33,10 +33,10 @@ export class JuniorResponseDto { profilePicture!: DocumentMetaResponseDto | null; @ApiProperty({ example: 2000.0, description: 'The available balance' }) - availableBalance!: number; + availableBalance!: number | null; constructor(junior: Junior) { - const card = junior.customer.cards?.[0]; + const card = junior.customer?.cards?.[0]; this.id = junior.id; this.firstName = junior.customer.firstName; this.lastName = junior.customer.lastName; @@ -45,7 +45,7 @@ export class JuniorResponseDto { this.dateOfBirth = junior.customer.dateOfBirth; this.relationship = junior.relationship; this.guardianRelationship = GuardianRelationship[junior.relationship]; - this.availableBalance = card ? Math.min(card.limit, card.account.balance) : 0; + this.availableBalance = card ? Math.min(card.limit, card.account.balance) : null; this.profilePicture = junior.customer.user.profilePicture ? new DocumentMetaResponseDto(junior.customer.user.profilePicture) : null;