Skip to content

Commit

Permalink
Add route to create card
Browse files Browse the repository at this point in the history
  • Loading branch information
henriqueleite42 committed Nov 27, 2023
1 parent 45c49ae commit dc8a8dd
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 11 deletions.
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ model Card {
transactions Transaction[]
recurrentTransactions RecurrentTransaction[]
@@unique([cardProviderId, lastFourDigits])
@@map("cards")
}

Expand Down
19 changes: 17 additions & 2 deletions src/delivery/card.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { Body, Controller, Get, Post, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from './guards/auth.guard';
import { PaginatedDto } from './dtos';
import { PaginatedDto, UserDataDto } from './dtos';
import { CardService } from 'src/usecases/card/card.service';
import { UserData } from './decorators/user-data';
import { CreateDto } from './dtos/card';

@Controller('cards')
@UseGuards(AuthGuard())
Expand All @@ -15,4 +17,17 @@ export class CardController {
) {
return this.cardService.getProviders(pagination);
}

@Post('/')
create(
@UserData()
userData: UserDataDto,
@Body()
body: CreateDto,
) {
return this.cardService.create({
...body,
accountId: userData.accountId,
});
}
}
41 changes: 41 additions & 0 deletions src/delivery/dtos/card.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {
IsInt,
IsNumberString,
IsOptional,
Max,
MaxLength,
Min,
MinLength,
} from 'class-validator';
import { IsID, IsName } from '../validators/internal';

export class CreateDto {
@IsID()
cardProviderId: string;

@IsName()
name: string;

@IsNumberString()
@MinLength(4)
@MaxLength(4)
lastFourDigits: string;

@IsOptional()
@IsInt()
@Min(1)
@Max(31)
dueDay?: number;

@IsOptional()
@IsInt()
@Min(0)
@Max(999_999_999_99)
limit?: number;

@IsOptional()
@IsInt()
@Min(0)
@Max(999_999_999_99)
balance?: number;
}
22 changes: 21 additions & 1 deletion src/models/card.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CardProvider } from '@prisma/client';
import { Card, CardProvider } from '@prisma/client';
import {
Paginated,
PaginatedItems,
Expand All @@ -13,8 +13,26 @@ import {
*
*/

export interface GetProviderInput {
cardProviderId: string;
}

export interface CreateInput {
accountId: string;
cardProviderId: string;
name: string;
lastFourDigits: string;
dueDay?: number;
limit?: number;
balance?: number;
}

export abstract class CardRepository {
abstract getProviders(i: PaginatedRepository): Promise<Array<CardProvider>>;

abstract getProvider(i: GetProviderInput): Promise<CardProvider | undefined>;

abstract create(i: CreateInput): Promise<Card>;
}

/**
Expand All @@ -27,4 +45,6 @@ export abstract class CardRepository {

export abstract class CardUseCase {
abstract getProviders(i: Paginated): Promise<PaginatedItems<CardProvider>>;

abstract create(i: CreateInput): Promise<void>;
}
5 changes: 3 additions & 2 deletions src/repositories/postgres/card/card-repository.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { PostgresModule } from '..';
import { CardRepositoryService } from './card-repository.service';
import { UIDAdapter } from 'src/adapters/implementations/uid.service';

@Module({
imports: [PostgresModule.forFeature(['cardProvider'])],
providers: [CardRepositoryService],
imports: [PostgresModule.forFeature(['cardProvider', 'card'])],
providers: [CardRepositoryService, UIDAdapter],
exports: [CardRepositoryService],
})
export class CardRepositoryModule {}
66 changes: 63 additions & 3 deletions src/repositories/postgres/card/card-repository.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { Injectable } from '@nestjs/common';
import {
ConflictException,
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository, Repository } from '..';
import { PaginatedRepository } from 'src/types/paginated-items';
import { CardRepository } from 'src/models/card';
import { CardProvider } from '@prisma/client';
import { CardRepository, CreateInput, GetProviderInput } from 'src/models/card';
import { Card, CardProvider } from '@prisma/client';
import { UIDAdapter } from 'src/adapters/implementations/uid.service';

@Injectable()
export class CardRepositoryService extends CardRepository {
constructor(
@InjectRepository('cardProvider')
private readonly cardProviderRepository: Repository<'cardProvider'>,
@InjectRepository('card')
private readonly cardRepository: Repository<'card'>,

private readonly idAdapter: UIDAdapter,
) {
super();
}
Expand All @@ -22,4 +32,54 @@ export class CardRepositoryService extends CardRepository {
take: limit,
});
}

getProvider({ cardProviderId }: GetProviderInput): Promise<CardProvider> {
return this.cardProviderRepository.findUnique({
where: {
id: cardProviderId,
},
});
}

async create({
accountId,
cardProviderId,
name,
lastFourDigits,
dueDay,
limit,
balance,
}: CreateInput): Promise<Card> {
try {
const cardAccount = await this.cardRepository.create({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
data: {
id: this.idAdapter.gen(),
accountId,
cardProviderId,
name,
lastFourDigits,
dueDay,
limit,
balance,
},
});

return cardAccount;
} catch (err) {
// https://www.prisma.io/docs/reference/api-reference/error-reference#p2003
if (err.code === 'P2003') {
throw new NotFoundException("Card provider doesn't exists");
}
// https://www.prisma.io/docs/reference/api-reference/error-reference#p2004
if (err.code === 'P2004') {
throw new ConflictException('Card already exists');
}

throw new InternalServerErrorException(
`Fail to create card: ${err.message}`,
);
}
}
}
57 changes: 54 additions & 3 deletions src/usecases/card/card.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Inject, Injectable } from '@nestjs/common';
import { CardProvider } from '@prisma/client';
import {
BadRequestException,
Inject,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { CardProvider, CardTypeEnum } from '@prisma/client';
import { UtilsAdapter } from 'src/adapters/implementations/utils.service';
import { CardUseCase } from 'src/models/card';
import { CardUseCase, CreateInput } from 'src/models/card';
import { CardRepositoryService } from 'src/repositories/postgres/card/card-repository.service';
import { Paginated, PaginatedItems } from 'src/types/paginated-items';

Expand All @@ -26,4 +31,50 @@ export class CardService extends CardUseCase {
data,
};
}

async create(i: CreateInput): Promise<void> {
const provider = await this.cardRepository.getProvider({
cardProviderId: i.cardProviderId,
});

if (!provider) {
throw new NotFoundException("Card provider doesn't exists");
}

if (this.isPostpaid(provider.type)) {
if (typeof i.dueDay === 'undefined' || typeof i.limit === 'undefined') {
throw new BadRequestException(
'Postpaid cards must have "dueDay" and "limit"',
);
}
if (typeof i.balance !== 'undefined') {
throw new BadRequestException('Postpaid cards can\'t have "balance"');
}
}

if (this.isPrepaid(provider.type)) {
if (typeof i.balance === 'undefined') {
throw new BadRequestException('Prepaid cards must have "balance"');
}
if (typeof i.dueDay !== 'undefined' || typeof i.limit !== 'undefined') {
throw new BadRequestException(
'Prepaid cards can\'t have "dueDay" and "limit"',
);
}
}

await this.cardRepository.create(i);
}

// Private

isPostpaid(type: CardTypeEnum) {
return [CardTypeEnum.CREDIT].includes(type as any);
}

isPrepaid(type: CardTypeEnum) {
return [CardTypeEnum.VA, CardTypeEnum.VR, CardTypeEnum.VT].includes(
type as any,
);
}
}

0 comments on commit dc8a8dd

Please sign in to comment.