├── .gitignore ├── README.md ├── jest.config.js ├── package.json ├── src ├── gof │ ├── behavioral │ │ ├── chain_of_responsibility │ │ │ ├── FareCalculator.ts │ │ │ ├── Ride.ts │ │ │ ├── Segment.ts │ │ │ └── calculateRide.ts │ │ ├── command │ │ │ ├── BankAccount.ts │ │ │ ├── BankAccountRepository.ts │ │ │ ├── Command.ts │ │ │ ├── GetBalance.ts │ │ │ ├── MakeTransfer.ts │ │ │ ├── Transaction.ts │ │ │ └── TransferCommand.ts │ │ ├── iterator │ │ │ ├── GenericIterator.ts │ │ │ ├── Iterator.ts │ │ │ ├── State.ts │ │ │ ├── UTXO.ts │ │ │ └── Wallet.ts │ │ ├── mediator │ │ │ ├── Average.ts │ │ │ ├── AverageRepository.ts │ │ │ ├── CalculateAverage.ts │ │ │ ├── GetAverage.ts │ │ │ ├── Grade.ts │ │ │ ├── GradeRepository.ts │ │ │ ├── Mediator.ts │ │ │ ├── SaveGrade.ts │ │ │ ├── SaveGradeMediator.ts │ │ │ └── create.sql │ │ ├── state │ │ │ ├── Ticket.ts │ │ │ └── TicketStatus.ts │ │ └── strategy │ │ │ ├── Checkin.ts │ │ │ ├── Checkout.ts │ │ │ ├── FareCalculator.ts │ │ │ ├── ParkingTicket.ts │ │ │ ├── ParkingTicketRepository.ts │ │ │ └── create.sql │ ├── creational │ │ ├── abstract_factory │ │ │ ├── ApplyForLoan.ts │ │ │ ├── GetLoan.ts │ │ │ ├── Installment.ts │ │ │ ├── InstallmentCalculator.ts │ │ │ ├── InstallmentRepository.ts │ │ │ ├── Loan.ts │ │ │ ├── LoanFactory.ts │ │ │ ├── LoanRepository.ts │ │ │ └── RepositoryFactory.ts │ │ ├── builder │ │ │ ├── FlightTicket.ts │ │ │ └── FlightTicketBuilder.ts │ │ ├── factory_method │ │ │ ├── CalculateFare.ts │ │ │ ├── Coord.ts │ │ │ ├── Location.ts │ │ │ ├── Ride.ts │ │ │ ├── RideRepository.ts │ │ │ ├── Segment.ts │ │ │ ├── SegmentRepository.ts │ │ │ └── UpdateLocation.ts │ │ ├── prototype │ │ │ ├── CopyForm.ts │ │ │ ├── Field.ts │ │ │ ├── Form.ts │ │ │ ├── FormRepository.ts │ │ │ └── Prototype.ts │ │ └── singleton │ │ │ ├── Login.ts │ │ │ ├── Signup.ts │ │ │ ├── User.ts │ │ │ └── UserRepository.ts │ └── structural │ │ ├── adapter │ │ ├── catalog │ │ │ ├── GetProduct.ts │ │ │ ├── HttpServer.ts │ │ │ ├── Product.ts │ │ │ ├── ProductRepository.ts │ │ │ └── api.ts │ │ └── checkout │ │ │ ├── CalculateCheckout.ts │ │ │ ├── CatalogGateway.ts │ │ │ ├── HttpClient.ts │ │ │ ├── Item.ts │ │ │ └── Order.ts │ │ ├── bridge │ │ ├── Account.ts │ │ ├── Driver.ts │ │ ├── Passenger.ts │ │ └── Password.ts │ │ ├── composite │ │ └── main.ts │ │ ├── decorator │ │ ├── BookRoom.ts │ │ ├── Booking.ts │ │ ├── BookingRepository.ts │ │ ├── CancelBooking.ts │ │ ├── GetBookingByCode.ts │ │ ├── ImportBooking.ts │ │ ├── LogDecorator.ts │ │ ├── Room.ts │ │ ├── RoomRepository.ts │ │ ├── SecurityDecorator.ts │ │ ├── Usecase.ts │ │ └── create.sql │ │ └── flyweight │ │ └── main.ts └── poeaa │ ├── domain_model │ ├── ApplyForLoan.ts │ ├── GetLoan.ts │ ├── Installment.ts │ ├── InstallmentCalculator.ts │ ├── InstallmentRepository.ts │ ├── Loan.ts │ ├── LoanFactory.ts │ ├── LoanRepository.ts │ └── RepositoryFactory.ts │ ├── orm │ ├── AccountModel.ts │ ├── DatabaseConnection.ts │ └── ORM.ts │ ├── repository │ ├── Email.ts │ ├── Password.ts │ ├── User.ts │ ├── UserRepository.ts │ └── create.sql │ └── transaction_script │ ├── controller │ └── LoanController.ts │ ├── dao │ ├── InstallmentDAO.ts │ └── LoanDAO.ts │ ├── main.ts │ └── service │ ├── InstallmentService.ts │ ├── Loan.ts │ └── LoanService.ts ├── test ├── gof │ ├── behavioral │ │ ├── chain_of_responsibility │ │ │ ├── Ride.test.ts │ │ │ └── calculateRide.test.ts │ │ ├── command │ │ │ ├── BankAccount.test.ts │ │ │ └── MakeTransfer.test.ts │ │ ├── iterator │ │ │ └── Wallet.test.ts │ │ ├── mediator │ │ │ └── SaveGrade.test.ts │ │ ├── state │ │ │ └── Ticket.test.ts │ │ └── strategy │ │ │ ├── Checkout.test.ts │ │ │ └── ParkingTicket.test.ts │ ├── creational │ │ ├── abstract_factory │ │ │ ├── ApplyForLoan.test.ts │ │ │ ├── InstallmentCalculator.test.ts │ │ │ └── Loan.test.ts │ │ ├── builder │ │ │ └── FlightTicket.test.ts │ │ ├── factory_method │ │ │ ├── Ride.test.ts │ │ │ ├── TimeSegment.test.ts │ │ │ └── UpdateLocation.test.ts │ │ ├── prototype │ │ │ └── CopyForm.test.ts │ │ └── singleton │ │ │ └── Signup.test.ts │ └── structural │ │ ├── adapter │ │ ├── catalog │ │ │ └── api.test.ts │ │ └── checkout │ │ │ └── CalculateCheckout.test.ts │ │ ├── bridge │ │ └── Account.test.ts │ │ └── decorator │ │ ├── BookRoom.test.ts │ │ ├── ImportBooking.test.ts │ │ └── RoomRepository.test.ts └── poeaa │ ├── orm │ └── ORM.test.ts │ ├── repository │ ├── User.test.ts │ └── UserRepository.test.ts │ └── transaction_script │ └── main.test.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | data 4 | resources -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Esse repositório é do curso de Design Patterns do Rodrigo Branas 2 | Mais informações em https://www.branas.io 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "design_patterns_course", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "@hapi/hapi": "^21.3.7", 8 | "@types/axios": "^0.14.0", 9 | "@types/express": "^4.17.21", 10 | "@types/jest": "^29.5.11", 11 | "@types/node-fetch": "2", 12 | "@types/pg-promise": "^5.4.3", 13 | "aws-sdk": "^2.1589.0", 14 | "axios": "^1.6.5", 15 | "currency.js": "^2.0.4", 16 | "express": "^4.19.2", 17 | "jest": "^29.7.0", 18 | "node-fetch": "2", 19 | "nodemon": "^3.0.2", 20 | "pg-promise": "^11.5.5", 21 | "pretty-bytes": "^6.1.1", 22 | "ts-jest": "^29.1.1", 23 | "ts-node": "^10.9.2", 24 | "typescript": "^5.3.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/gof/behavioral/chain_of_responsibility/FareCalculator.ts: -------------------------------------------------------------------------------- 1 | import Segment from "./Segment"; 2 | 3 | export default interface FareCalculator { 4 | next?: FareCalculator; 5 | calculate (segment: Segment): number; 6 | } 7 | 8 | export class NormalFareCalculator implements FareCalculator { 9 | FARE = 2.1; 10 | 11 | constructor (readonly next?: FareCalculator) { 12 | } 13 | 14 | calculate(segment: Segment): number { 15 | if (!segment.isOvernight() && !segment.isSunday()) { 16 | return segment.distance * this.FARE; 17 | } 18 | if (!this.next) throw new Error(); 19 | return this.next.calculate(segment); 20 | } 21 | 22 | } 23 | 24 | export class OvernightFareCalculator implements FareCalculator { 25 | FARE = 3.9; 26 | 27 | constructor (readonly next?: FareCalculator) { 28 | } 29 | 30 | calculate(segment: Segment): number { 31 | if (segment.isOvernight() && !segment.isSunday()) { 32 | return segment.distance * this.FARE; 33 | } 34 | if (!this.next) throw new Error(); 35 | return this.next.calculate(segment); 36 | } 37 | 38 | } 39 | 40 | export class SundayFareCalculator implements FareCalculator { 41 | FARE = 2.9; 42 | 43 | constructor (readonly next?: FareCalculator) { 44 | } 45 | 46 | calculate(segment: Segment): number { 47 | if (!segment.isOvernight() && segment.isSunday()) { 48 | return segment.distance * this.FARE; 49 | } 50 | if (!this.next) throw new Error(); 51 | return this.next.calculate(segment); 52 | } 53 | 54 | } 55 | 56 | export class OvernightSundayFareCalculator implements FareCalculator { 57 | FARE = 5; 58 | 59 | constructor (readonly next?: FareCalculator) { 60 | } 61 | 62 | calculate(segment: Segment): number { 63 | if (segment.isOvernight() && segment.isSunday()) { 64 | return segment.distance * this.FARE; 65 | } 66 | if (!this.next) throw new Error(); 67 | return this.next.calculate(segment); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/gof/behavioral/chain_of_responsibility/Ride.ts: -------------------------------------------------------------------------------- 1 | import FareCalculator from "./FareCalculator"; 2 | import Segment from "./Segment"; 3 | 4 | export default class Ride { 5 | private segments: Segment[]; 6 | private fare: number; 7 | 8 | constructor (readonly fareCalculator: FareCalculator) { 9 | this.segments = []; 10 | this.fare = 0; 11 | } 12 | 13 | addSegment (distance: number, date: Date) { 14 | this.segments.push(new Segment(distance, date)); 15 | } 16 | 17 | calculateFare () { 18 | this.fare = 0; 19 | for (const segment of this.segments) { 20 | this.fare += this.fareCalculator.calculate(segment); 21 | } 22 | this.fare = (this.fare < 10) ? 10 : this.fare; 23 | } 24 | 25 | getFare () { 26 | return this.fare; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/gof/behavioral/chain_of_responsibility/Segment.ts: -------------------------------------------------------------------------------- 1 | export default class Segment { 2 | 3 | constructor (readonly distance: number, readonly date: Date) { 4 | if (!this.isValidDistance()) throw new Error("Invalid distance"); 5 | if (!this.isValidDate()) throw new Error("Invalid date"); 6 | } 7 | 8 | isValidDistance () { 9 | return this.distance != null && this.distance != undefined && typeof this.distance === "number" && this.distance > 0; 10 | } 11 | 12 | isValidDate () { 13 | return this.date != null && this.date != undefined && this.date instanceof Date && this.date.toString() !== "Invalid Date"; 14 | } 15 | 16 | isOvernight () { 17 | return this.date.getHours() >= 22 || this.date.getHours() <= 6; 18 | } 19 | 20 | isSunday () { 21 | return this.date.getDay() === 0; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/gof/behavioral/chain_of_responsibility/calculateRide.ts: -------------------------------------------------------------------------------- 1 | export function calculateFare (segments: any[]) { 2 | let fare = 0; 3 | for (const segment of segments) { 4 | if (!isValidDistance(segment.distance)) throw new Error("Invalid distance"); 5 | if (!isValidDate(segment.date)) throw new Error("Invalid date"); 6 | if (isOvernight(segment.date) && !isSunday(segment.date)) { 7 | fare += segment.distance * 3.90; 8 | } 9 | if (isOvernight(segment.date) && isSunday(segment.date)) { 10 | fare += segment.distance * 5; 11 | } 12 | if (!isOvernight(segment.date) && isSunday(segment.date)) { 13 | fare += segment.distance * 2.9; 14 | } 15 | if (!isOvernight(segment.date) && !isSunday(segment.date)) { 16 | fare += segment.distance * 2.10; 17 | } 18 | } 19 | return (fare < 10) ? 10 : fare; 20 | } 21 | 22 | function isValidDistance (distance: number) { 23 | return distance != null && distance != undefined && typeof distance === "number" && distance > 0; 24 | } 25 | 26 | function isValidDate (date: Date) { 27 | return date != null && date != undefined && date instanceof Date && date.toString() !== "Invalid Date"; 28 | } 29 | 30 | function isOvernight (date: Date) { 31 | return date.getHours() >= 22 || date.getHours() <= 6; 32 | } 33 | 34 | function isSunday (date: Date) { 35 | return date.getDay() === 0; 36 | } 37 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/BankAccount.ts: -------------------------------------------------------------------------------- 1 | import Transaction from "./Transaction"; 2 | 3 | export default class BankAccount { 4 | transactions: Transaction[]; 5 | 6 | constructor (readonly bankAccountId: number) { 7 | this.transactions = []; 8 | } 9 | 10 | debit (amount: number) { 11 | this.transactions.push(new Transaction("debit", amount)); 12 | } 13 | 14 | credit (amount: number) { 15 | this.transactions.push(new Transaction("credit", amount)); 16 | } 17 | 18 | getBalance() { 19 | let total = 0; 20 | for (const transaction of this.transactions) { 21 | if (transaction.type === "credit") total += transaction.amount; 22 | if (transaction.type === "debit") total -= transaction.amount; 23 | } 24 | return total; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/BankAccountRepository.ts: -------------------------------------------------------------------------------- 1 | import BankAccount from "./BankAccount"; 2 | 3 | export default interface BankAccountRepository { 4 | save (bankAccount: BankAccount): Promise; 5 | update (bankAccount: BankAccount): Promise; 6 | getById (bankAccountId: number): Promise; 7 | } 8 | 9 | export class BankAccountRepositoryMemory implements BankAccountRepository { 10 | bankAccounts: BankAccount[]; 11 | 12 | constructor () { 13 | this.bankAccounts = []; 14 | } 15 | 16 | async save(bankAccount: BankAccount): Promise { 17 | this.bankAccounts.push(bankAccount); 18 | } 19 | 20 | async update(bankAccount: BankAccount): Promise { 21 | const index = this.bankAccounts.findIndex((existingBankAccount: BankAccount) => existingBankAccount.bankAccountId === bankAccount.bankAccountId); 22 | this.bankAccounts.splice(index, 1); 23 | this.bankAccounts.push(bankAccount); 24 | } 25 | 26 | async getById(bankAccountId: number): Promise { 27 | const bankAccount = this.bankAccounts.find((bankAccount: BankAccount) => bankAccount.bankAccountId === bankAccountId); 28 | if (!bankAccount) throw new Error("Bank account not found"); 29 | return bankAccount; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/Command.ts: -------------------------------------------------------------------------------- 1 | export default interface Command { 2 | execute (): void; 3 | } 4 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/GetBalance.ts: -------------------------------------------------------------------------------- 1 | import BankAccountRepository from "./BankAccountRepository" 2 | 3 | export default class GetBalance { 4 | 5 | constructor (readonly bankAccountRepository: BankAccountRepository) { 6 | } 7 | 8 | async execute (bankAccountId: number): Promise { 9 | const bankAccount = await this.bankAccountRepository.getById(bankAccountId); 10 | return { 11 | balance: bankAccount.getBalance() 12 | } 13 | } 14 | } 15 | 16 | type Output = { 17 | balance: number 18 | } -------------------------------------------------------------------------------- /src/gof/behavioral/command/MakeTransfer.ts: -------------------------------------------------------------------------------- 1 | import BankAccountRepository from "./BankAccountRepository" 2 | import TransferCommand from "./TransferCommand"; 3 | 4 | export default class MakeTransfer { 5 | 6 | constructor (readonly bankAccountRepository: BankAccountRepository) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const from = await this.bankAccountRepository.getById(input.fromBankAccountId); 11 | const to = await this.bankAccountRepository.getById(input.toBankAccountId); 12 | const transferCommand = new TransferCommand(from, to, input.amount); 13 | transferCommand.execute(); 14 | await this.bankAccountRepository.update(from); 15 | await this.bankAccountRepository.update(to); 16 | } 17 | } 18 | 19 | type Input = { 20 | fromBankAccountId: number, 21 | toBankAccountId: number, 22 | amount: number 23 | } 24 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/Transaction.ts: -------------------------------------------------------------------------------- 1 | export default class Transaction { 2 | 3 | constructor (readonly type: "credit" | "debit", readonly amount: number) { 4 | } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/gof/behavioral/command/TransferCommand.ts: -------------------------------------------------------------------------------- 1 | import BankAccount from "./BankAccount"; 2 | import Command from "./Command"; 3 | 4 | export default class TransferCommand implements Command { 5 | 6 | constructor (readonly from: BankAccount, readonly to: BankAccount, readonly amount: number) { 7 | } 8 | 9 | execute(): void { 10 | this.from.debit(this.amount); 11 | this.to.credit(this.amount); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/gof/behavioral/iterator/GenericIterator.ts: -------------------------------------------------------------------------------- 1 | import Iterator from "./Iterator"; 2 | 3 | export default class GenericIterator implements Iterator { 4 | position: number; 5 | 6 | constructor (readonly elements: any[]) { 7 | this.position = 0; 8 | } 9 | 10 | next() { 11 | return this.elements[this.position++]; 12 | } 13 | 14 | hasNext(): boolean { 15 | return this.position < this.elements.length; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/gof/behavioral/iterator/Iterator.ts: -------------------------------------------------------------------------------- 1 | export default interface Iterator { 2 | next (): any; 3 | hasNext (): boolean; 4 | } -------------------------------------------------------------------------------- /src/gof/behavioral/iterator/State.ts: -------------------------------------------------------------------------------- 1 | import UTXO from "./UTXO"; 2 | import Wallet from "./Wallet"; 3 | 4 | export default class State { 5 | wallets: { [address: string]: Wallet }; 6 | 7 | constructor () { 8 | this.wallets = {}; 9 | } 10 | 11 | addWallet (wallet: Wallet) { 12 | this.wallets[wallet.address] = wallet; 13 | } 14 | 15 | transfer (from: string, to: string, amount: number) { 16 | const walletFrom = this.wallets[from]; 17 | const walletTo = this.wallets[to]; 18 | let totalUtxos = 0; 19 | if (walletFrom.getBalance() < amount) throw new Error("Insuficient funds"); 20 | for (const utxo of walletFrom.utxos) { 21 | totalUtxos += utxo.amount; 22 | walletFrom.removeUTXO(utxo); 23 | if (totalUtxos >= amount) break; 24 | } 25 | const remaining = totalUtxos - amount; 26 | if (remaining < 0) throw new Error("Double spend transaction detected"); 27 | walletFrom.addUTXO(new UTXO(remaining)); 28 | walletTo.addUTXO(new UTXO(amount)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/gof/behavioral/iterator/UTXO.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | export default class UTXO { 4 | id: string; 5 | 6 | constructor (readonly amount: number) { 7 | this.id = crypto.randomUUID(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/gof/behavioral/iterator/Wallet.ts: -------------------------------------------------------------------------------- 1 | import GenericIterator from "./GenericIterator"; 2 | import UTXO from "./UTXO"; 3 | 4 | export default class Wallet { 5 | utxos: UTXO[]; 6 | 7 | constructor (readonly address: string) { 8 | this.utxos = []; 9 | } 10 | 11 | addUTXO (utxo: UTXO) { 12 | this.utxos.push(utxo); 13 | } 14 | 15 | removeUTXO (utxo: UTXO) { 16 | this.utxos.splice(this.utxos.indexOf(utxo), 1); 17 | } 18 | 19 | getBalance () { 20 | let balance = 0; 21 | const iterator = new GenericIterator(this.utxos); 22 | while (iterator.hasNext()) { 23 | const utxo = iterator.next(); 24 | balance += utxo.amount; 25 | } 26 | return balance; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/Average.ts: -------------------------------------------------------------------------------- 1 | export default class Average { 2 | 3 | constructor (readonly studentId: number, readonly value: number) { 4 | } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/AverageRepository.ts: -------------------------------------------------------------------------------- 1 | import Average from "./Average"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface AverageRepository { 5 | save (average: Average): Promise; 6 | getByStudentId (studentId: number): Promise; 7 | } 8 | 9 | export class AverageRepositoryDatabase implements AverageRepository { 10 | 11 | async save(average: Average): Promise { 12 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 13 | await connection.query("delete from design_patterns.average where student_id = $1", [average.studentId]); 14 | await connection.query("insert into design_patterns.average (student_id, value) values ($1, $2)", [average.studentId, average.value]); 15 | await connection.$pool.end(); 16 | } 17 | 18 | async getByStudentId(studentId: number): Promise { 19 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 20 | const [averageData] = await connection.query("select * from design_patterns.average where student_id = $1", [studentId]); 21 | await connection.$pool.end(); 22 | return new Average(averageData.student_id, parseFloat(averageData.value)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/CalculateAverage.ts: -------------------------------------------------------------------------------- 1 | import Average from "./Average"; 2 | import AverageRepository from "./AverageRepository"; 3 | import GradeRepository from "./GradeRepository"; 4 | 5 | export default class CalculateAverage { 6 | 7 | constructor (readonly gradeRepository: GradeRepository, readonly averageRepository: AverageRepository) { 8 | } 9 | 10 | async execute (studentId: number): Promise { 11 | const grades = await this.gradeRepository.listByStudentId(studentId); 12 | let total = 0; 13 | for (const grade of grades) { 14 | total += grade.value; 15 | } 16 | const value = total/grades.length; 17 | const average = new Average(studentId, value); 18 | await this.averageRepository.save(average); 19 | } 20 | } -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/GetAverage.ts: -------------------------------------------------------------------------------- 1 | import Average from "./Average"; 2 | import AverageRepository from "./AverageRepository"; 3 | 4 | export default class GetAverage { 5 | 6 | constructor (readonly averageRepository: AverageRepository) { 7 | } 8 | 9 | async execute (studentId: number): Promise { 10 | const average = await this.averageRepository.getByStudentId(studentId); 11 | return { 12 | average: average.value 13 | } 14 | } 15 | } 16 | 17 | type Output = { 18 | average: number 19 | } 20 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/Grade.ts: -------------------------------------------------------------------------------- 1 | export default class Grade { 2 | 3 | constructor (readonly studentId: number, readonly exam: string, readonly value: number) { 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/GradeRepository.ts: -------------------------------------------------------------------------------- 1 | import Grade from "./Grade"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface GradeRepository { 5 | save (grade: Grade): Promise; 6 | listByStudentId (studentId: number): Promise; 7 | } 8 | 9 | export class GradeRepositoryDatabase implements GradeRepository { 10 | 11 | async save(grade: Grade): Promise { 12 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 13 | await connection.query("insert into design_patterns.grade (student_id, exam, value) values ($1, $2, $3)", [grade.studentId, grade.exam, grade.value]); 14 | await connection.$pool.end(); 15 | } 16 | 17 | async listByStudentId(studentId: number): Promise { 18 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 19 | const gradesData = await connection.query("select * from design_patterns.grade where student_id = $1", [studentId]); 20 | await connection.$pool.end(); 21 | const grades = []; 22 | for (const gradeData of gradesData) { 23 | grades.push(new Grade(gradeData.student_id, gradeData.exam, parseFloat(gradeData.value))); 24 | } 25 | return grades; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/Mediator.ts: -------------------------------------------------------------------------------- 1 | export default class Mediator { 2 | handlers: { event: string, callback: Function }[]; 3 | 4 | constructor () { 5 | this.handlers = []; 6 | } 7 | 8 | register (event: string, callback: Function) { 9 | this.handlers.push({ event, callback }); 10 | } 11 | 12 | async notify (event: string, data: any) { 13 | for (const handler of this.handlers) { 14 | if (handler.event === event) { 15 | await handler.callback(data); 16 | } 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/SaveGrade.ts: -------------------------------------------------------------------------------- 1 | import CalculateAverage from "./CalculateAverage"; 2 | import Grade from "./Grade"; 3 | import GradeRepository from "./GradeRepository"; 4 | 5 | export default class SaveGrade { 6 | 7 | constructor (readonly gradeRepository: GradeRepository, readonly calculateAverage: CalculateAverage) { 8 | } 9 | 10 | async execute (input: Input): Promise { 11 | const grade = new Grade(input.studentId, input.exam, input.value); 12 | await this.gradeRepository.save(grade); 13 | await this.calculateAverage.execute(input.studentId); 14 | } 15 | } 16 | 17 | type Input = { 18 | studentId: number, 19 | exam: string, 20 | value: number 21 | } 22 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/SaveGradeMediator.ts: -------------------------------------------------------------------------------- 1 | import Grade from "./Grade"; 2 | import GradeRepository from "./GradeRepository"; 3 | import Mediator from "./Mediator"; 4 | 5 | export default class SaveGrade { 6 | 7 | constructor (readonly gradeRepository: GradeRepository, readonly mediator: Mediator) { 8 | } 9 | 10 | async execute (input: Input): Promise { 11 | const grade = new Grade(input.studentId, input.exam, input.value); 12 | await this.gradeRepository.save(grade); 13 | await this.mediator.notify("gradeSaved", { studentId: input.studentId }); 14 | } 15 | } 16 | 17 | type Input = { 18 | studentId: number, 19 | exam: string, 20 | value: number 21 | } 22 | -------------------------------------------------------------------------------- /src/gof/behavioral/mediator/create.sql: -------------------------------------------------------------------------------- 1 | drop schema design_patterns cascade; 2 | create schema design_patterns; 3 | 4 | create table design_patterns.grade ( 5 | student_id integer, 6 | exam text, 7 | value numeric 8 | ); 9 | 10 | create table design_patterns.average ( 11 | student_id integer, 12 | value numeric 13 | ); 14 | -------------------------------------------------------------------------------- /src/gof/behavioral/state/Ticket.ts: -------------------------------------------------------------------------------- 1 | import TicketStatus, { RequestedStatus } from "./TicketStatus"; 2 | 3 | export default class Ticket { 4 | status: TicketStatus; 5 | employeeId?: number; 6 | assignDate?: Date; 7 | startDate?: Date; 8 | endDate?: Date; 9 | 10 | constructor (readonly customerId: number, readonly requestDate: Date) { 11 | this.status = new RequestedStatus(this); 12 | } 13 | 14 | assign (employeeId: number, assignDate: Date) { 15 | this.status.assign(); 16 | this.employeeId = employeeId; 17 | this.assignDate = assignDate; 18 | } 19 | 20 | start (startDate: Date) { 21 | this.startDate = startDate; 22 | this.status.start(); 23 | } 24 | 25 | close (endDate: Date) { 26 | this.endDate = endDate; 27 | this.status.close(); 28 | } 29 | 30 | getStatus () { 31 | return this.status.value; 32 | } 33 | 34 | getStatistics (currentDate: Date) { 35 | let assignDuration = 0; 36 | const requestDuration = ((this.assignDate || currentDate).getTime() - this.requestDate.getTime())/(1000*60*60); 37 | if (this.assignDate) assignDuration = ((this.startDate || currentDate).getTime() - this.assignDate.getTime())/(1000*60*60); 38 | return { 39 | requestDuration, 40 | assignDuration 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/gof/behavioral/state/TicketStatus.ts: -------------------------------------------------------------------------------- 1 | import Ticket from "./Ticket"; 2 | 3 | export default interface TicketStatus { 4 | value: string; 5 | 6 | assign (): void; 7 | start (): void; 8 | close (): void; 9 | } 10 | 11 | export class RequestedStatus implements TicketStatus { 12 | value: string; 13 | 14 | constructor (readonly ticket: Ticket) { 15 | this.value = "requested"; 16 | } 17 | 18 | assign(): void { 19 | this.ticket.status = new AssignedStatus(this.ticket); 20 | } 21 | 22 | start(): void { 23 | throw new Error("Could not start ticket"); 24 | } 25 | 26 | close(): void { 27 | throw new Error("Could not close ticket"); 28 | } 29 | 30 | } 31 | 32 | export class AssignedStatus implements TicketStatus { 33 | value: string; 34 | 35 | constructor (readonly ticket: Ticket) { 36 | this.value = "assigned"; 37 | } 38 | 39 | assign(): void { 40 | throw new Error("Could not assign ticket"); 41 | } 42 | 43 | start(): void { 44 | this.ticket.status = new InProgressStatus(this.ticket); 45 | } 46 | 47 | close(): void { 48 | throw new Error("Could not close ticket"); 49 | } 50 | 51 | } 52 | 53 | export class InProgressStatus implements TicketStatus { 54 | value: string; 55 | 56 | constructor (readonly ticket: Ticket) { 57 | this.value = "in_progress"; 58 | } 59 | 60 | assign(): void { 61 | throw new Error("Could not assign ticket"); 62 | } 63 | 64 | start(): void { 65 | throw new Error("Could not start ticket"); 66 | } 67 | 68 | close(): void { 69 | this.ticket.status = new ClosedStatus(this.ticket); 70 | } 71 | 72 | } 73 | 74 | export class ClosedStatus implements TicketStatus { 75 | value: string; 76 | 77 | constructor (readonly ticket: Ticket) { 78 | this.value = "closed"; 79 | } 80 | 81 | assign(): void { 82 | throw new Error("Could not assign ticket"); 83 | } 84 | 85 | start(): void { 86 | throw new Error("Could not start ticket"); 87 | } 88 | 89 | close(): void { 90 | throw new Error("Could not closed ticket"); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/Checkin.ts: -------------------------------------------------------------------------------- 1 | import ParkingTicket from "./ParkingTicket" 2 | import ParkingTicketRepository from "./ParkingTicketRepository" 3 | 4 | export default class Checkin { 5 | 6 | constructor (readonly parkingTicketRepository: ParkingTicketRepository) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const existingTicket = await this.parkingTicketRepository.getByPlate(input.plate); 11 | if (existingTicket) throw new Error("Duplicated plate"); 12 | const parkingTicket = new ParkingTicket(input.plate, input.checkinDate, input.location); 13 | await this.parkingTicketRepository.save(parkingTicket); 14 | } 15 | } 16 | 17 | type Input = { 18 | plate: string, 19 | checkinDate: Date, 20 | location: string 21 | } 22 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/Checkout.ts: -------------------------------------------------------------------------------- 1 | import ParkingTicket from "./ParkingTicket" 2 | import ParkingTicketRepository from "./ParkingTicketRepository" 3 | 4 | export default class Checkout { 5 | 6 | constructor (readonly parkingTicketRepository: ParkingTicketRepository) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const parkingTicket = await this.parkingTicketRepository.getByPlate(input.plate); 11 | if (!parkingTicket) throw new Error("Parking ticket not found"); 12 | parkingTicket.checkout(input.checkoutDate); 13 | await this.parkingTicketRepository.update(parkingTicket); 14 | return { 15 | plate: parkingTicket.plate, 16 | fare: parkingTicket.fare 17 | } 18 | } 19 | } 20 | 21 | type Input = { 22 | plate: string, 23 | checkoutDate: Date 24 | } 25 | 26 | type Output = { 27 | plate: string, 28 | fare: number 29 | } 30 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/FareCalculator.ts: -------------------------------------------------------------------------------- 1 | export default interface FareCalculator { 2 | calculate (checkinDate: Date, checkoutDate: Date): number; 3 | } 4 | 5 | export class AirportFareCalculator implements FareCalculator { 6 | 7 | calculate(checkinDate: Date, checkoutDate: Date): number { 8 | const diff = (checkoutDate.getTime() - checkinDate.getTime())/(1000*60*60); 9 | return diff * 10; 10 | } 11 | 12 | } 13 | 14 | export class ShoppingFareCalculator implements FareCalculator { 15 | 16 | calculate(checkinDate: Date, checkoutDate: Date): number { 17 | const diff = (checkoutDate.getTime() - checkinDate.getTime())/(1000*60*60); 18 | let fare = 10; 19 | const remainingHours = diff - 3; 20 | if (remainingHours > 0) fare += remainingHours * 10; 21 | return fare; 22 | } 23 | 24 | } 25 | 26 | export class BeachFareCalculator implements FareCalculator { 27 | 28 | calculate(checkinDate: Date, checkoutDate: Date): number { 29 | return 10; 30 | } 31 | 32 | } 33 | 34 | export class PublicFareCalculator implements FareCalculator { 35 | 36 | calculate(checkinDate: Date, checkoutDate: Date): number { 37 | return 0; 38 | } 39 | 40 | } 41 | 42 | export class FareCalculatorFactory { 43 | static create (location: string): FareCalculator { 44 | if (location === "airport") return new AirportFareCalculator(); 45 | if (location === "shopping") return new ShoppingFareCalculator(); 46 | if (location === "beach") return new BeachFareCalculator(); 47 | if (location === "public") return new PublicFareCalculator(); 48 | throw new Error("Location not found"); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/ParkingTicket.ts: -------------------------------------------------------------------------------- 1 | import { FareCalculatorFactory } from "./FareCalculator"; 2 | 3 | export default class ParkingTicket { 4 | fare: number; 5 | checkoutDate?: Date; 6 | 7 | constructor (readonly plate: string, readonly checkinDate: Date, readonly location: string) { 8 | this.fare = 0; 9 | } 10 | 11 | checkout (checkoutDate: Date) { 12 | this.checkoutDate = checkoutDate; 13 | const fareCalculator = FareCalculatorFactory.create(this.location); 14 | this.fare = fareCalculator.calculate(this.checkinDate, this.checkoutDate); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/ParkingTicketRepository.ts: -------------------------------------------------------------------------------- 1 | import ParkingTicket from "./ParkingTicket"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface ParkingTicketRepository { 5 | getByPlate (plate: string): Promise; 6 | save (parkingTicket: ParkingTicket): Promise; 7 | update (parkingTicket: ParkingTicket): Promise; 8 | } 9 | 10 | export class ParkingTicketRepositoryDatabase implements ParkingTicketRepository { 11 | 12 | async getByPlate(plate: string): Promise { 13 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 14 | const [parkingTicketData] = await connection.query("select * from design_patterns.parking_ticket where plate = $1", [plate]); 15 | await connection.$pool.end(); 16 | if (!parkingTicketData) return; 17 | return new ParkingTicket(parkingTicketData.plate, parkingTicketData.checkin_date, parkingTicketData.location); 18 | } 19 | 20 | async save(parkingTicket: ParkingTicket): Promise { 21 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 22 | await connection.query("insert into design_patterns.parking_ticket (plate, checkin_date, location, fare) values ($1, $2, $3, $4)", [parkingTicket.plate, parkingTicket.checkinDate, parkingTicket.location, parkingTicket.fare]); 23 | await connection.$pool.end(); 24 | } 25 | 26 | async update(parkingTicket: ParkingTicket): Promise { 27 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 28 | await connection.query("update design_patterns.parking_ticket set checkout_date = $1, fare = $2 where plate = $3", [parkingTicket.checkoutDate, parkingTicket.fare, parkingTicket.plate]) 29 | await connection.$pool.end(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gof/behavioral/strategy/create.sql: -------------------------------------------------------------------------------- 1 | drop schema design_patterns cascade; 2 | create schema design_patterns; 3 | 4 | create table design_patterns.parking_ticket ( 5 | plate text, 6 | checkin_date timestamp, 7 | checkout_date timestamp, 8 | fare numeric, 9 | location text 10 | ); 11 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/ApplyForLoan.ts: -------------------------------------------------------------------------------- 1 | import InstallmentRepository from "./InstallmentRepository"; 2 | import LoanFactory from "./LoanFactory"; 3 | import LoanRepository from "./LoanRepository"; 4 | import RepositoryFactory from "./RepositoryFactory"; 5 | 6 | export default class ApplyForLoan { 7 | loanRepository: LoanRepository; 8 | installmentRepository: InstallmentRepository; 9 | 10 | constructor (readonly repositoryFactory: RepositoryFactory, readonly loanFactory: LoanFactory) { 11 | this.loanRepository = repositoryFactory.createLoanRepository(); 12 | this.installmentRepository = repositoryFactory.createInstallmentRepository(); 13 | } 14 | 15 | async execute (input: Input): Promise { 16 | const loan = this.loanFactory.createLoan(input.amount, input.income, input.installments); 17 | const installmentCalculator = this.loanFactory.createInstallmentCalculator(); 18 | const installments = installmentCalculator.calculate(loan); 19 | await this.loanRepository.save(loan); 20 | for (const installment of installments) { 21 | await this.installmentRepository.save(installment); 22 | } 23 | return { 24 | loanId: loan.loanId 25 | }; 26 | } 27 | } 28 | 29 | type Input = { 30 | amount: number, 31 | income: number, 32 | installments: number 33 | } 34 | 35 | type Output = { 36 | loanId: string 37 | } 38 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/GetLoan.ts: -------------------------------------------------------------------------------- 1 | import { SACInstallmentCalculator } from "./InstallmentCalculator"; 2 | import InstallmentRepository from "./InstallmentRepository"; 3 | import { MortgageLoan } from "./Loan" 4 | import LoanRepository from "./LoanRepository"; 5 | import RepositoryFactory from "./RepositoryFactory"; 6 | 7 | export default class GetLoan { 8 | loanRepository: LoanRepository; 9 | installmentRepository: InstallmentRepository; 10 | 11 | constructor (readonly repositoryFactory: RepositoryFactory) { 12 | this.loanRepository = repositoryFactory.createLoanRepository(); 13 | this.installmentRepository = repositoryFactory.createInstallmentRepository(); 14 | } 15 | 16 | async execute (input: Input): Promise { 17 | const loan = await this.loanRepository.getById(input.loanId); 18 | const installments = await this.installmentRepository.listByLoanId(input.loanId); 19 | const output: Output = { 20 | amount: loan.amount, 21 | income: loan.income, 22 | installments: [] 23 | } 24 | for (const installment of installments) { 25 | output.installments.push({ 26 | number: installment.number, 27 | amount: installment.amount, 28 | amortization: installment.amortization, 29 | interest: installment.interest, 30 | balance: installment.balance 31 | }); 32 | } 33 | return output; 34 | } 35 | } 36 | 37 | type Input = { 38 | loanId: string 39 | } 40 | 41 | type Output = { 42 | amount: number, 43 | income: number, 44 | installments: { number: number, amount: number, amortization: number, interest: number, balance: number }[] 45 | } 46 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/Installment.ts: -------------------------------------------------------------------------------- 1 | export default class Installment { 2 | 3 | constructor (readonly loanId: string, readonly number: number, readonly amount: number, readonly amortization: number, readonly interest: number, readonly balance: number) { 4 | 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/InstallmentCalculator.ts: -------------------------------------------------------------------------------- 1 | import currency from "currency.js"; 2 | import Installment from "./Installment"; 3 | import Loan from "./Loan"; 4 | 5 | export default interface InstallmentCalculator { 6 | calculate (loan: Loan): Installment[]; 7 | } 8 | 9 | export class SACInstallmentCalculator implements InstallmentCalculator { 10 | 11 | calculate(loan: Loan): Installment[] { 12 | const installments: Installment[] = []; 13 | let balance = currency(loan.amount); 14 | const rate = (loan.rate / 100) / 12; 15 | let installmentNumber = 1; 16 | let amortization = currency(balance.value / loan.installments); 17 | while (balance.value > 0.10) { 18 | let interest = currency(balance.value * rate); 19 | let updatedBalance = currency(balance.value + interest.value); 20 | let amount = currency(interest.value + amortization.value); 21 | balance = currency(updatedBalance.value - amount.value); 22 | if (balance.value < 0.10) balance = currency(0); 23 | installments.push(new Installment( 24 | loan.loanId, 25 | installmentNumber, 26 | amount.value, 27 | amortization.value, 28 | interest.value, 29 | balance.value 30 | )); 31 | installmentNumber++; 32 | } 33 | return installments; 34 | } 35 | 36 | } 37 | 38 | export class PriceInstallmentCalculator implements InstallmentCalculator { 39 | 40 | calculate(loan: Loan): Installment[] { 41 | const installments: Installment[] = []; 42 | let balance = currency(loan.amount); 43 | const rate = (loan.rate / 100) / 12; 44 | let installmentNumber = 1; 45 | const formula = Math.pow((1 + rate), loan.installments); 46 | let amount = balance.multiply((formula * rate) / (formula - 1)); 47 | while (balance.value > 2) { 48 | let interest = balance.multiply(rate); 49 | let amortization = amount.subtract(interest); 50 | balance = balance.subtract(amortization); 51 | if (balance.value < 2) balance = currency(0); 52 | installments.push(new Installment( 53 | loan.loanId, 54 | installmentNumber, 55 | amount.value, 56 | amortization.value, 57 | interest.value, 58 | balance.value 59 | )); 60 | installmentNumber++; 61 | } 62 | return installments; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/InstallmentRepository.ts: -------------------------------------------------------------------------------- 1 | import Installment from "./Installment"; 2 | import Loan from "./Loan"; 3 | 4 | export default interface InstallmentRepository { 5 | save (installment: Installment): Promise; 6 | listByLoanId (loanId: string): Promise; 7 | } 8 | 9 | export class InstallmentRepositoryMemory implements InstallmentRepository { 10 | installments: Installment[]; 11 | static instance: InstallmentRepository; 12 | 13 | private constructor () { 14 | this.installments = []; 15 | } 16 | 17 | async save(installment: Installment): Promise { 18 | this.installments.push(installment); 19 | } 20 | 21 | async listByLoanId(loanId: string): Promise { 22 | const installments = this.installments.filter((installment: Installment) => installment.loanId === loanId); 23 | return installments; 24 | } 25 | 26 | static getInstance () { 27 | if (!InstallmentRepositoryMemory.instance) { 28 | InstallmentRepositoryMemory.instance = new InstallmentRepositoryMemory(); 29 | } 30 | return InstallmentRepositoryMemory.instance; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/Loan.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | export default abstract class Loan { 4 | abstract rate: number; 5 | 6 | constructor (readonly loanId: string, readonly amount: number, readonly income: number, readonly installments: number, readonly type: string) { 7 | } 8 | 9 | static create (amount: number, income: number, installments: number) { 10 | throw new Error("This method is abstract"); 11 | } 12 | } 13 | 14 | export class MortgageLoan extends Loan { 15 | rate = 10; 16 | 17 | constructor (loanId: string, amount: number, income: number, installments: number) { 18 | super(loanId, amount, income, installments, "mortgage"); 19 | if (installments > 420) throw new Error("The maximum number of installments for mortgage loan is 420"); 20 | if ((income * 0.25) < amount/installments) throw new Error("The installment amount could not exceed 25% of monthly income"); 21 | } 22 | 23 | static create (amount: number, income: number, installments: number) { 24 | const loanId = crypto.randomUUID(); 25 | return new MortgageLoan(loanId, amount, income, installments); 26 | } 27 | } 28 | 29 | export class CarLoan extends Loan { 30 | rate = 15; 31 | 32 | constructor (loanId: string, amount: number, income: number, installments: number) { 33 | super(loanId, amount, income, installments, "mortgage"); 34 | if (installments > 60) throw new Error("The maximum number of installments for car loan is 60"); 35 | if ((income * 0.30) < amount/installments) throw new Error("The installment amount could not exceed 30% of monthly income"); 36 | } 37 | 38 | static create (amount: number, income: number, installments: number) { 39 | const loanId = crypto.randomUUID(); 40 | return new CarLoan(loanId, amount, income, installments); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/LoanFactory.ts: -------------------------------------------------------------------------------- 1 | import InstallmentCalculator, { PriceInstallmentCalculator, SACInstallmentCalculator } from "./InstallmentCalculator"; 2 | import Loan, { CarLoan, MortgageLoan } from "./Loan"; 3 | 4 | export default interface LoanFactory { 5 | createLoan(amount: number, income: number, installments: number): Loan; 6 | createInstallmentCalculator(): InstallmentCalculator; 7 | } 8 | 9 | export class MortgageLoanFactory implements LoanFactory { 10 | 11 | createLoan(amount: number, income: number, installments: number): MortgageLoan { 12 | return MortgageLoan.create(amount, income, installments); 13 | } 14 | 15 | createInstallmentCalculator(): InstallmentCalculator { 16 | return new SACInstallmentCalculator(); 17 | } 18 | 19 | } 20 | 21 | export class CarLoanFactory implements LoanFactory { 22 | 23 | createLoan(amount: number, income: number, installments: number): CarLoan { 24 | return CarLoan.create(amount, income, installments); 25 | } 26 | 27 | createInstallmentCalculator(): InstallmentCalculator { 28 | return new PriceInstallmentCalculator(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/LoanRepository.ts: -------------------------------------------------------------------------------- 1 | import Loan from "./Loan"; 2 | 3 | export default interface LoanRepository { 4 | save (loan: Loan): Promise; 5 | getById (loanId: string): Promise; 6 | } 7 | 8 | export class LoanRepositoryMemory implements LoanRepository { 9 | loans: Loan[]; 10 | static instance: LoanRepository; 11 | 12 | private constructor () { 13 | this.loans = []; 14 | } 15 | 16 | async save(loan: Loan): Promise { 17 | this.loans.push(loan); 18 | } 19 | 20 | async getById(loanId: string): Promise { 21 | const loan = this.loans.find((loan: Loan) => loan.loanId === loanId); 22 | if (!loan) throw new Error("Loan not found"); 23 | return loan; 24 | } 25 | 26 | static getInstance () { 27 | if (!LoanRepositoryMemory.instance) { 28 | LoanRepositoryMemory.instance = new LoanRepositoryMemory(); 29 | } 30 | return LoanRepositoryMemory.instance; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/gof/creational/abstract_factory/RepositoryFactory.ts: -------------------------------------------------------------------------------- 1 | import InstallmentRepository, { InstallmentRepositoryMemory } from "./InstallmentRepository"; 2 | import LoanRepository, { LoanRepositoryMemory } from "./LoanRepository"; 3 | 4 | export default interface RepositoryFactory { 5 | createLoanRepository (): LoanRepository; 6 | createInstallmentRepository (): InstallmentRepository; 7 | } 8 | 9 | export class RepositoryMemoryFactory implements RepositoryFactory { 10 | 11 | createLoanRepository(): LoanRepository { 12 | return LoanRepositoryMemory.getInstance(); 13 | } 14 | 15 | createInstallmentRepository(): InstallmentRepository { 16 | return InstallmentRepositoryMemory.getInstance(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/gof/creational/builder/FlightTicket.ts: -------------------------------------------------------------------------------- 1 | import FlightTicketBuilder from "./FlightTicketBuilder"; 2 | 3 | export default class FlightTicket { 4 | airline: string; 5 | flightCode: string; 6 | fromAirport: string; 7 | toAirport: string; 8 | passengerName: string; 9 | passengerEmail: string; 10 | passengerDocument: string; 11 | passengerGender: string; 12 | emergencyContactName: string; 13 | emergencyContactTelephone: string; 14 | seat: string; 15 | checkedBags: number; 16 | hasCheckin: boolean; 17 | terminal: string; 18 | gate: string; 19 | priority: number; 20 | 21 | constructor (builder: FlightTicketBuilder) { 22 | this.airline = builder.airline; 23 | this.fromAirport = builder.fromAirport; 24 | this.toAirport = builder.toAirport; 25 | this.flightCode = builder.flightCode; 26 | this.passengerName = builder.passengerName; 27 | this.passengerEmail = builder.passengerEmail; 28 | this.passengerDocument = builder.passengerDocument; 29 | this.passengerGender = builder.passengerGender; 30 | this.emergencyContactName = builder.emergencyContactName; 31 | this.emergencyContactTelephone = builder.emergencyContactTelephone; 32 | this.seat = builder.seat; 33 | this.checkedBags = builder.checkedBags; 34 | this.hasCheckin = builder.hasCheckin; 35 | this.terminal = builder.terminal; 36 | this.gate = builder.gate; 37 | this.priority = builder.priority; 38 | } 39 | } -------------------------------------------------------------------------------- /src/gof/creational/builder/FlightTicketBuilder.ts: -------------------------------------------------------------------------------- 1 | import FlightTicket from "./FlightTicket"; 2 | 3 | export default class FlightTicketBuilder { 4 | airline!: string; 5 | flightCode!: string; 6 | fromAirport!: string; 7 | toAirport!: string; 8 | passengerName!: string; 9 | passengerEmail!: string; 10 | passengerDocument!: string; 11 | passengerGender!: string; 12 | emergencyContactName!: string; 13 | emergencyContactTelephone!: string; 14 | seat!: string; 15 | checkedBags!: number; 16 | hasCheckin!: boolean; 17 | terminal!: string; 18 | gate!: string; 19 | priority!: number; 20 | 21 | 22 | setFlight (airline: string, code: string) { 23 | this.airline = airline; 24 | this.flightCode = code; 25 | return this; 26 | } 27 | 28 | setTrip (from: string, to: string) { 29 | this.fromAirport = from; 30 | this.toAirport = to; 31 | return this; 32 | } 33 | 34 | setPassenger (name: string, email: string, document: string, gender: string) { 35 | this.passengerName = name; 36 | this.passengerEmail = email; 37 | this.passengerDocument = document; 38 | this.passengerGender = gender; 39 | return this; 40 | } 41 | 42 | setEmergencyContact (name: string, telephone: string) { 43 | this.emergencyContactName = name; 44 | this.emergencyContactTelephone = telephone; 45 | return this; 46 | } 47 | 48 | setSeat (seat: string) { 49 | this.seat = seat; 50 | return this; 51 | } 52 | 53 | setCheckedBags (checkedBags: number) { 54 | this.checkedBags = checkedBags; 55 | return this; 56 | } 57 | 58 | setCheckinInformation (hasCheckin: boolean, terminal: string, gate: string) { 59 | this.hasCheckin = hasCheckin; 60 | this.terminal = terminal; 61 | this.gate = gate; 62 | return this; 63 | } 64 | 65 | setPriority (priority: number) { 66 | this.priority = priority; 67 | return this; 68 | } 69 | 70 | getFlightTicket (): FlightTicket { 71 | return new FlightTicket(this); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/CalculateFare.ts: -------------------------------------------------------------------------------- 1 | import RideRepository from "./RideRepository"; 2 | import SegmentRepository from "./SegmentRepository"; 3 | 4 | export default class CalculateFare { 5 | 6 | constructor (readonly rideRepository: RideRepository, readonly segmentRepository: SegmentRepository) { 7 | } 8 | 9 | async execute (rideId: string): Promise { 10 | const ride = await this.rideRepository.getById(rideId); 11 | const segments = await this.segmentRepository.listByRideId(rideId); 12 | const fare = ride.calculateFare(segments); 13 | return { 14 | fare 15 | } 16 | 17 | } 18 | } 19 | type Output = { 20 | fare: number 21 | } 22 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/Coord.ts: -------------------------------------------------------------------------------- 1 | export default class Coord { 2 | 3 | constructor (readonly lat: number, readonly long: number) { 4 | if (lat < -90 || lat > 90) throw new Error("Invalid latitude"); 5 | if (long < -180 || long > 180) throw new Error("Invalid longitude"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/Location.ts: -------------------------------------------------------------------------------- 1 | import Coord from "./Coord"; 2 | 3 | export default class Location { 4 | coord: Coord; 5 | 6 | constructor (lat: number, long: number, readonly date: Date) { 7 | this.coord = new Coord(lat, long); 8 | } 9 | } -------------------------------------------------------------------------------- /src/gof/creational/factory_method/Ride.ts: -------------------------------------------------------------------------------- 1 | import Location from "./Location"; 2 | import crypto from "crypto"; 3 | import Segment, { DistanceSegment, TimeSegment } from "./Segment"; 4 | 5 | export default abstract class Ride { 6 | lastLocation: Location; 7 | 8 | constructor (readonly rideId: string, lat: number, long: number, date: Date) { 9 | this.lastLocation = new Location(lat, long, date); 10 | } 11 | 12 | updateLocation (newLocation: Location) { 13 | this.lastLocation = newLocation; 14 | } 15 | 16 | abstract calculateFare (segments: Segment[]): number; 17 | abstract createSegment (from: Location, to: Location): Segment; 18 | } 19 | 20 | export class DistanceRide extends Ride { 21 | 22 | calculateFare (segments: DistanceSegment[]) { 23 | let total = 0; 24 | for (const segment of segments) { 25 | total += segment.getDistance(); 26 | } 27 | return total * 4; 28 | } 29 | 30 | createSegment(from: Location, to: Location): Segment { 31 | return new DistanceSegment(this.rideId, from, to); 32 | } 33 | 34 | static create (lat: number, long: number, date: Date) { 35 | const rideId = crypto.randomUUID(); 36 | return new DistanceRide(rideId, lat, long, date); 37 | } 38 | } 39 | 40 | export class TimeRide extends Ride { 41 | 42 | calculateFare (segments: TimeSegment[]) { 43 | let total = 0; 44 | for (const segment of segments) { 45 | total += segment.getDiffInMinutes(); 46 | } 47 | return total * 1; 48 | } 49 | 50 | createSegment(from: Location, to: Location): Segment { 51 | return new TimeSegment(this.rideId, from, to); 52 | } 53 | 54 | static create (lat: number, long: number, date: Date) { 55 | const rideId = crypto.randomUUID(); 56 | return new TimeRide(rideId, lat, long, date); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/RideRepository.ts: -------------------------------------------------------------------------------- 1 | import Ride from "./Ride"; 2 | 3 | export default interface RideRepository { 4 | save (ride: Ride): Promise; 5 | update (ride: Ride): Promise; 6 | getById (rideId: string): Promise; 7 | } 8 | 9 | export class RideRepositoryMemory implements RideRepository { 10 | rides: Ride[]; 11 | 12 | constructor () { 13 | this.rides = []; 14 | } 15 | 16 | async save(ride: Ride): Promise { 17 | this.rides.push(ride); 18 | } 19 | 20 | async update(ride: Ride): Promise { 21 | const index = this.rides.findIndex((existingRide: Ride) => existingRide.rideId === ride.rideId); 22 | this.rides[index] = ride; 23 | } 24 | 25 | async getById(rideId: string): Promise { 26 | const ride = this.rides.find((ride: Ride) => ride.rideId === rideId); 27 | if (!ride) throw new Error("Ride not found"); 28 | return ride; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/Segment.ts: -------------------------------------------------------------------------------- 1 | import Location from "./Location"; 2 | 3 | export default abstract class Segment { 4 | 5 | constructor (readonly rideId: string, readonly from: Location, readonly to: Location) { 6 | } 7 | } 8 | 9 | export class DistanceSegment extends Segment { 10 | 11 | getDistance () { 12 | const earthRadius = 6371; 13 | const degreesToRadians = Math.PI / 180; 14 | const deltaLat = (this.to.coord.lat - this.from.coord.lat) * degreesToRadians; 15 | const deltaLon = (this.to.coord.long - this.from.coord.long) * degreesToRadians; 16 | const a = 17 | Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + 18 | Math.cos(this.from.coord.lat * degreesToRadians) * 19 | Math.cos(this.to.coord.lat * degreesToRadians) * 20 | Math.sin(deltaLon / 2) * 21 | Math.sin(deltaLon / 2); 22 | const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 23 | return Math.round(earthRadius * c); 24 | } 25 | } 26 | 27 | export class TimeSegment extends Segment { 28 | 29 | getDiffInMinutes () { 30 | return (this.to.date.getTime() - this.from.date.getTime()) / (1000 * 60); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/SegmentRepository.ts: -------------------------------------------------------------------------------- 1 | import Segment from "./Segment"; 2 | 3 | export default interface SegmentRepository { 4 | save (segment: Segment): Promise; 5 | listByRideId (rideId: string): Promise; 6 | } 7 | 8 | export class SegmentRepositoryMemory implements SegmentRepository { 9 | segments: Segment[]; 10 | 11 | constructor () { 12 | this.segments = []; 13 | } 14 | 15 | async save(segment: Segment): Promise { 16 | this.segments.push(segment); 17 | } 18 | 19 | async listByRideId(rideId: string): Promise { 20 | return this.segments.filter((segment: Segment) => segment.rideId === rideId); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/gof/creational/factory_method/UpdateLocation.ts: -------------------------------------------------------------------------------- 1 | import Location from "./Location"; 2 | import RideRepository from "./RideRepository"; 3 | import SegmentRepository from "./SegmentRepository"; 4 | 5 | export default class UpdateLocation { 6 | 7 | constructor (readonly rideRepository: RideRepository, readonly segmentRepository: SegmentRepository) { 8 | } 9 | 10 | async execute (input: Input): Promise { 11 | const ride = await this.rideRepository.getById(input.rideId); 12 | const newLocation = new Location(input.lat, input.long, input.date); 13 | const segment = ride.createSegment(ride.lastLocation, newLocation); 14 | ride.updateLocation(newLocation); 15 | await this.rideRepository.update(ride); 16 | await this.segmentRepository.save(segment); 17 | } 18 | } 19 | type Input = { 20 | rideId: string, 21 | lat: number, 22 | long: number, 23 | date: Date 24 | } 25 | -------------------------------------------------------------------------------- /src/gof/creational/prototype/CopyForm.ts: -------------------------------------------------------------------------------- 1 | import FormRepository from "./FormRepository"; 2 | 3 | export default class CopyForm { 4 | 5 | constructor (readonly formRepository: FormRepository) { 6 | } 7 | 8 | async execute (input: Input) { 9 | const form = await this.formRepository.getById(input.fromFormId); 10 | const newForm = form.clone(); 11 | newForm.formId = input.newFormId; 12 | newForm.category = input.newCategory; 13 | newForm.description = input.newDescription; 14 | await this.formRepository.save(newForm); 15 | } 16 | } 17 | 18 | type Input = { 19 | fromFormId: string, 20 | newFormId: string, 21 | newCategory: string, 22 | newDescription: string 23 | } -------------------------------------------------------------------------------- /src/gof/creational/prototype/Field.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | import Prototype from "./Prototype"; 3 | 4 | export default class Field implements Prototype { 5 | 6 | constructor (readonly fieldId: string, readonly type: string, readonly title: string) { 7 | } 8 | 9 | static create (type: string, title: string) { 10 | const fieldId = crypto.randomUUID(); 11 | return new Field(fieldId, type, title); 12 | } 13 | 14 | clone(): Field { 15 | return new Field(this.fieldId, this.type, this.title); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/gof/creational/prototype/Form.ts: -------------------------------------------------------------------------------- 1 | import Field from "./Field"; 2 | import Prototype from "./Prototype"; 3 | 4 | export default class Form implements Prototype { 5 | fields: Field[]; 6 | 7 | constructor (public formId: string, public category: string, public description: string) { 8 | this.fields = []; 9 | } 10 | 11 | addField (type: string, title: string) { 12 | this.fields.push(Field.create(type, title)); 13 | } 14 | 15 | clone(): Form { 16 | const newForm = new Form(this.formId, this.category, this.description); 17 | const fields: Field[] = []; 18 | for (const field of this.fields) { 19 | fields.push(field.clone()); 20 | } 21 | newForm.fields = fields; 22 | return newForm; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gof/creational/prototype/FormRepository.ts: -------------------------------------------------------------------------------- 1 | import Form from "./Form"; 2 | 3 | export default interface FormRepository { 4 | getById (formId: string): Promise
; 5 | save (form: Form): Promise; 6 | } 7 | 8 | export class FormRepositoryMemory implements FormRepository { 9 | forms: Form[]; 10 | 11 | constructor () { 12 | this.forms = []; 13 | } 14 | 15 | async getById(formId: string): Promise { 16 | const form = this.forms.find((form: Form) => form.formId === formId); 17 | if (!form) throw new Error("Form not found"); 18 | return form; 19 | } 20 | 21 | async save(form: Form): Promise { 22 | this.forms.push(form); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/gof/creational/prototype/Prototype.ts: -------------------------------------------------------------------------------- 1 | export default interface Prototype { 2 | clone (): Prototype; 3 | } 4 | -------------------------------------------------------------------------------- /src/gof/creational/singleton/Login.ts: -------------------------------------------------------------------------------- 1 | import UserRepository, { UserRepositoryMemory } from "./UserRepository"; 2 | 3 | export default class Login { 4 | userRepository: UserRepository; 5 | 6 | constructor () { 7 | this.userRepository = UserRepositoryMemory.getInstance(); 8 | } 9 | 10 | async execute (input: Input): Promise { 11 | const user = await this.userRepository.getByEmail(input.email); 12 | let success = false; 13 | if (user && user.passwordMatches(input.password)) { 14 | success = true; 15 | } 16 | return { 17 | success 18 | } 19 | } 20 | } 21 | 22 | type Input = { 23 | email: string, 24 | password: string 25 | } 26 | 27 | type Output = { 28 | success: boolean 29 | } 30 | -------------------------------------------------------------------------------- /src/gof/creational/singleton/Signup.ts: -------------------------------------------------------------------------------- 1 | import User from "./User"; 2 | import UserRepository, { UserRepositoryMemory } from "./UserRepository"; 3 | 4 | export default class Signup { 5 | userRepository: UserRepository; 6 | 7 | constructor () { 8 | this.userRepository = UserRepositoryMemory.getInstance(); 9 | } 10 | 11 | async execute (input: Input): Promise { 12 | const user = User.create(input.name, input.email, input.password); 13 | await this.userRepository.save(user); 14 | } 15 | } 16 | 17 | type Input = { 18 | name: string, 19 | email: string, 20 | password: string 21 | } 22 | -------------------------------------------------------------------------------- /src/gof/creational/singleton/User.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | export default class User { 4 | 5 | constructor (readonly userId: string, readonly name: string, readonly email: string, readonly password: string) { 6 | } 7 | 8 | static create (name: string, email: string, password: string) { 9 | const userId = crypto.randomUUID(); 10 | return new User(userId, name, email, password); 11 | } 12 | 13 | passwordMatches (password: string) { 14 | return this.password === password; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gof/creational/singleton/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import User from "./User"; 2 | 3 | export default interface UserRepository { 4 | save (user: User): Promise; 5 | getByEmail (email: string): Promise; 6 | } 7 | 8 | export class UserRepositoryMemory implements UserRepository { 9 | private users: User[]; 10 | static instance: UserRepositoryMemory; 11 | 12 | private constructor () { 13 | this.users = []; 14 | } 15 | 16 | async save(user: User): Promise { 17 | this.users.push(user); 18 | } 19 | 20 | async getByEmail(email: string): Promise { 21 | const user = this.users.find((user: User) => user.email === email); 22 | if (!user) return; 23 | return user; 24 | } 25 | 26 | static getInstance () { 27 | if (!UserRepositoryMemory.instance) { 28 | UserRepositoryMemory.instance = new UserRepositoryMemory(); 29 | } 30 | return UserRepositoryMemory.instance; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/catalog/GetProduct.ts: -------------------------------------------------------------------------------- 1 | import ProductRepository from "./ProductRepository"; 2 | 3 | export default class GetProduct { 4 | 5 | constructor (readonly productRepository: ProductRepository) { 6 | } 7 | 8 | async execute (productId: number): Promise { 9 | const product = await this.productRepository.getById(productId); 10 | return { 11 | productId: product.productId, 12 | description: product.description, 13 | price: product.price 14 | } 15 | } 16 | } 17 | 18 | type Output = { 19 | productId: number, 20 | description: string, 21 | price: number 22 | } 23 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/catalog/HttpServer.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from "express"; 2 | import Hapi from "@hapi/hapi"; 3 | 4 | export default interface HttpServer { 5 | register (method: string, url: string, callback: Function): void; 6 | listen (port: number): void; 7 | } 8 | 9 | export class ExpressAdapter implements HttpServer { 10 | app: any; 11 | 12 | constructor () { 13 | this.app = express(); 14 | } 15 | 16 | register(method: string, url: string, callback: Function): void { 17 | this.app[method](url.replace(/\{|\}/g, ""), async function (req: Request, res: Response) { 18 | const output = await callback(req.params, req.body); 19 | res.json(output); 20 | }); 21 | } 22 | 23 | listen(port: number): void { 24 | this.app.listen(port); 25 | } 26 | 27 | } 28 | 29 | export class HapiAdapter implements HttpServer { 30 | server: Hapi.Server; 31 | 32 | constructor () { 33 | this.server = Hapi.server({}); 34 | } 35 | 36 | register(method: string, url: string, callback: Function): void { 37 | this.server.route({ 38 | method, 39 | path: url.replace(/\:/g, ""), 40 | handler: async function (request: any, reply: any) { 41 | const output = await callback(request.params, request.body); 42 | return output; 43 | } 44 | }); 45 | } 46 | 47 | listen(port: number): void { 48 | this.server.settings.port = port; 49 | this.server.start(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/catalog/Product.ts: -------------------------------------------------------------------------------- 1 | export default class Product { 2 | 3 | constructor (readonly productId: number, readonly description: string, readonly price: number) { 4 | } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/catalog/ProductRepository.ts: -------------------------------------------------------------------------------- 1 | import Product from "./Product"; 2 | 3 | export default interface ProductRepository { 4 | getById (productId: number): Promise; 5 | } 6 | 7 | export class ProductRepositoryMemory implements ProductRepository { 8 | products = [ 9 | { 10 | productId: 1, 11 | description: "A", 12 | price: 100 13 | }, 14 | { 15 | productId: 2, 16 | description: "B", 17 | price: 200 18 | }, 19 | { 20 | productId: 3, 21 | description: "C", 22 | price: 300 23 | } 24 | ]; 25 | 26 | async getById(productId: number): Promise { 27 | const product = this.products.find((product: any) => product.productId === productId); 28 | if (!product) throw new Error("Product not found"); 29 | return product; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/catalog/api.ts: -------------------------------------------------------------------------------- 1 | import GetProduct from "./GetProduct"; 2 | import { ExpressAdapter, HapiAdapter } from "./HttpServer"; 3 | import { ProductRepositoryMemory } from "./ProductRepository"; 4 | 5 | const productRepository = new ProductRepositoryMemory(); 6 | const getProduct = new GetProduct(productRepository); 7 | const httpServer = new ExpressAdapter(); 8 | // const httpServer = new HapiAdapter(); 9 | httpServer.register("get", "/products/:{productId}", async function (params: any, body: any) { 10 | const productId = parseInt(params.productId); 11 | const output = await getProduct.execute(productId); 12 | return output; 13 | }); 14 | httpServer.listen(3001); 15 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/checkout/CalculateCheckout.ts: -------------------------------------------------------------------------------- 1 | import CatalogGateway from "./CatalogGateway" 2 | import Order from "./Order"; 3 | 4 | export default class CalculateCheckout { 5 | 6 | constructor (readonly catalogGateway: CatalogGateway) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const order = new Order(); 11 | for (const item of input.items) { 12 | const product = await this.catalogGateway.getProduct(item.productId); 13 | order.addProduct(product, item.quantity); 14 | } 15 | const total = order.getTotal(); 16 | return { 17 | total 18 | }; 19 | } 20 | } 21 | 22 | type Input = { 23 | items: { productId: number, quantity: number }[] 24 | } 25 | 26 | type Output = { 27 | total: number 28 | } 29 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/checkout/CatalogGateway.ts: -------------------------------------------------------------------------------- 1 | import HttpClient from "./HttpClient"; 2 | 3 | export default interface CatalogGateway { 4 | getProduct (productId: number): Promise; 5 | } 6 | 7 | export type ProductDTO = { 8 | productId: number, 9 | description: string, 10 | price: number 11 | } 12 | 13 | export class CatalogGatewayHttp implements CatalogGateway { 14 | 15 | constructor (readonly httpClient: HttpClient) { 16 | } 17 | 18 | async getProduct(productId: number): Promise { 19 | const response = await this.httpClient.get(`http://localhost:3001/products/${productId}`); 20 | return response; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/gof/structural/adapter/checkout/HttpClient.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import fetch from "node-fetch"; 3 | 4 | export default interface HttpClient { 5 | get (url: string): Promise; 6 | post (url: string, body: any): Promise; 7 | } 8 | 9 | export class AxiosAdapter implements HttpClient { 10 | 11 | async get(url: string): Promise { 12 | const response = await axios.get(url); 13 | return response.data; 14 | } 15 | 16 | async post(url: string, body: any): Promise { 17 | const response = await axios.post(url, body); 18 | return response.data; 19 | } 20 | 21 | } 22 | 23 | export class FetchAdapter implements HttpClient { 24 | 25 | async get(url: string): Promise { 26 | const response = await fetch(url); 27 | return response.json(); 28 | } 29 | 30 | async post(url: string, body: any): Promise { 31 | const response = await fetch(url, { 32 | method: "POST", 33 | headers: { 34 | "content-type": "application/json" 35 | }, 36 | body: JSON.stringify(body) 37 | }); 38 | return response.json(); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/checkout/Item.ts: -------------------------------------------------------------------------------- 1 | export default class Item { 2 | 3 | constructor (readonly productId: number, readonly price: number, readonly quantity: number) { 4 | } 5 | 6 | getTotal () { 7 | return this.price * this.quantity; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/gof/structural/adapter/checkout/Order.ts: -------------------------------------------------------------------------------- 1 | import { ProductDTO } from "./CatalogGateway"; 2 | import Item from "./Item"; 3 | 4 | export default class Order { 5 | items: Item[]; 6 | 7 | constructor () { 8 | this.items = []; 9 | } 10 | 11 | addProduct (product: ProductDTO, quantity: number) { 12 | this.items.push(new Item(product.productId, product.price, quantity)); 13 | } 14 | 15 | getTotal () { 16 | let total = 0; 17 | for (const item of this.items) { 18 | total += item.getTotal(); 19 | } 20 | return total; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/gof/structural/bridge/Account.ts: -------------------------------------------------------------------------------- 1 | import Password, { PasswordFactory } from "./Password"; 2 | 3 | export default abstract class Account { 4 | password: Password; 5 | 6 | constructor (readonly name: string, readonly email: string, readonly document: string, password: string, readonly passwordType: string) { 7 | if (!name.match(/.+ .+/g)) throw new Error("Invalid name"); 8 | if (!email.match(/.+\@.+\..+/g)) throw new Error("Invalid email"); 9 | if (document.length !== 11) throw new Error("Invalid document"); 10 | this.password = PasswordFactory.create(passwordType, password); 11 | } 12 | 13 | passwordMatches (password: string): boolean { 14 | return this.password.passwordMatches(password); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/gof/structural/bridge/Driver.ts: -------------------------------------------------------------------------------- 1 | import Account from "./Account"; 2 | 3 | 4 | export default class Driver extends Account { 5 | 6 | constructor (name: string, email: string, document: string, readonly carPlate: string, password: string, passwordType: string = "plaintext") { 7 | super(name, email, document, password, passwordType); 8 | if (!carPlate.match(/[A-Z]{3}[0-9]{4}/)) throw new Error("Invalid car plate"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/gof/structural/bridge/Passenger.ts: -------------------------------------------------------------------------------- 1 | import Account from "./Account"; 2 | 3 | export default class Passenger extends Account { 4 | 5 | constructor (name: string, email: string, document: string, readonly cardHolder: string, readonly cardNumber: string, readonly expDate: string, readonly cvv: string, password: string, passwordType: string = "plaintext") { 6 | super(name, email, document, password, passwordType); 7 | if (cvv.length !== 3) throw new Error("Invalid cvv"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/gof/structural/bridge/Password.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | export default interface Password { 4 | value: string; 5 | passwordMatches (password: string): boolean; 6 | } 7 | 8 | export class PasswordPlainText implements Password { 9 | value: string; 10 | 11 | constructor (password: string) { 12 | this.value = password; 13 | } 14 | 15 | passwordMatches(password: string): boolean { 16 | return this.value === password; 17 | } 18 | 19 | } 20 | 21 | export class PasswordSHA1 implements Password { 22 | value: string; 23 | 24 | constructor (password: string) { 25 | this.value = crypto.createHash("sha1").update(password).digest("hex"); 26 | } 27 | 28 | passwordMatches(password: string): boolean { 29 | return this.value === crypto.createHash("sha1").update(password).digest("hex"); 30 | } 31 | 32 | } 33 | 34 | export class PasswordFactory { 35 | static create (type: string, password: string) { 36 | if (type === "plaintext") return new PasswordPlainText(password); 37 | if (type === "sha1") return new PasswordSHA1(password); 38 | throw new Error("Invalid password type"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/gof/structural/composite/main.ts: -------------------------------------------------------------------------------- 1 | async function main (invoice: any) { 2 | let totalPurchases = 0; 3 | let totalEvents = 0; 4 | let totalPayments = 0; 5 | for (const purchase of invoice.purchases) { 6 | totalEvents += purchase.amount; 7 | totalPurchases += purchase.amount; 8 | } 9 | for (const penalty of invoice.penalties) { 10 | totalEvents += penalty.amount; 11 | } 12 | for (const interest of invoice.interests) { 13 | totalEvents += interest.amount; 14 | } 15 | for (const payment of invoice.payments) { 16 | totalPayments += payment.amount; 17 | } 18 | if (totalEvents !== totalPayments) throw new Error(""); 19 | let total = 0; 20 | let sharedPayments = []; 21 | const events: any = []; 22 | for (const purchase of invoice.purchases) { 23 | const share = purchase.amount/totalPurchases; 24 | events.push(purchase); 25 | for (const penalty of invoice.penalties) { 26 | const purchasePenalty = penalty.amount * share; 27 | events.push({ 28 | type: "penalty", 29 | amount: purchasePenalty, 30 | parent: purchase 31 | }); 32 | } 33 | for (const interest of invoice.interests) { 34 | const purchaseInterest = interest.amount * share; 35 | events.push({ 36 | type: "interest", 37 | amount: purchaseInterest, 38 | parent: purchase 39 | }); 40 | } 41 | } 42 | const payments = []; 43 | for (const payment of invoice.payments) { 44 | const share = payment.amount/totalEvents; 45 | for (const event of events) { 46 | const eventPayment = event.amount * share; 47 | payments.push({ 48 | type: "payment", 49 | amount: eventPayment, 50 | parent: event 51 | }); 52 | } 53 | } 54 | events.push(...payments); 55 | console.log(events); 56 | console.log(payments[11].type); 57 | console.log(payments[11].parent.type); 58 | console.log(payments[11].parent.parent.type); 59 | } 60 | 61 | const invoice = { 62 | month: 1, 63 | year: 2022, 64 | purchases: [ 65 | { type: "purchase", amount: 1000 }, 66 | { type: "purchase", amount: 500 } 67 | ], 68 | penalties: [ 69 | { type: "penalty", amount: 100 } 70 | ], 71 | interests: [ 72 | { type: "interest", amount: 100 } 73 | ], 74 | payments: [ 75 | { type: "payment", amount: 1200 }, 76 | { type: "payment", amount: 500 } 77 | ] 78 | } 79 | main(invoice); 80 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/BookRoom.ts: -------------------------------------------------------------------------------- 1 | import Booking from "./Booking"; 2 | import BookingRepository from "./BookingRepository"; 3 | import RoomRepository from "./RoomRepository"; 4 | import Usecase from "./Usecase"; 5 | 6 | export default class BookRoom implements Usecase { 7 | 8 | constructor (readonly roomRepository: RoomRepository, readonly bookingRepository: BookingRepository) { 9 | } 10 | 11 | async execute (input: Input): Promise { 12 | const [availableRoom] = await this.roomRepository.getAvailableRoomsByPeriodAndCategory(input.checkinDate, input.checkoutDate, input.category); 13 | if (!availableRoom) throw new Error("Room is not available"); 14 | const booking = Booking.create(availableRoom, input.email, input.checkinDate, input.checkoutDate); 15 | await this.bookingRepository.save(booking); 16 | return { 17 | code: booking.code 18 | } 19 | } 20 | } 21 | 22 | type Input = { 23 | email: string, 24 | checkinDate: Date, 25 | checkoutDate: Date, 26 | category: string 27 | } 28 | 29 | type Output = { 30 | code: string 31 | } 32 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/Booking.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | import Room from "./Room"; 3 | 4 | export default class Booking { 5 | 6 | constructor (readonly code: string, readonly roomId: number, readonly email: string, readonly checkinDate: Date, readonly checkoutDate: Date, readonly duration: number, readonly price: number, private status: string) { 7 | } 8 | 9 | static create (room: Room, email: string, checkinDate: Date, checkoutDate: Date) { 10 | const code = crypto.randomUUID(); 11 | const duration = (checkoutDate.getTime() - checkinDate.getTime())/(1000*60*60*24); 12 | const price = duration * room.price; 13 | const status = "confirmed"; 14 | return new Booking(code, room.roomId, email, checkinDate, checkoutDate, duration, price, status); 15 | } 16 | 17 | cancel () { 18 | this.status = "cancelled"; 19 | } 20 | 21 | getStatus () { 22 | return this.status; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/BookingRepository.ts: -------------------------------------------------------------------------------- 1 | import Booking from "./Booking"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface BookingRepository { 5 | save (booking: Booking): Promise; 6 | update (booking: Booking): Promise; 7 | getBookingByCode (code: string): Promise; 8 | } 9 | 10 | export class BookingRepositoryDatabase implements BookingRepository { 11 | 12 | async save(booking: Booking): Promise { 13 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 14 | await connection.query("insert into design_patterns.booking (code, room_id, email, checkin_date, checkout_date, duration, price, status) values ($1, $2, $3, $4, $5, $6, $7, $8)", [booking.code, booking.roomId, booking.email, booking.checkinDate, booking.checkoutDate, booking.duration, booking.price, booking.getStatus()]); 15 | await connection.$pool.end(); 16 | } 17 | 18 | async update(booking: Booking): Promise { 19 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 20 | await connection.query("update design_patterns.booking set status = $1 where code = $2", [booking.getStatus(), booking.code]); 21 | await connection.$pool.end(); 22 | } 23 | 24 | async getBookingByCode(code: string): Promise { 25 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 26 | const [bookingData] = await connection.query("select * from design_patterns.booking where code = $1", [code]); 27 | await connection.$pool.end(); 28 | return new Booking(bookingData.code, bookingData.room_id, bookingData.email, bookingData.checkin_date, bookingData.checkout_date, bookingData.duration, parseFloat(bookingData.price), bookingData.status); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/CancelBooking.ts: -------------------------------------------------------------------------------- 1 | import BookingRepository from "./BookingRepository"; 2 | import RoomRepository from "./RoomRepository"; 3 | 4 | export default class CancelBooking { 5 | 6 | constructor (readonly bookingRepository: BookingRepository) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const booking = await this.bookingRepository.getBookingByCode(input.code); 11 | booking.cancel(); 12 | await this.bookingRepository.update(booking); 13 | } 14 | } 15 | 16 | type Input = { 17 | code: string 18 | } -------------------------------------------------------------------------------- /src/gof/structural/decorator/GetBookingByCode.ts: -------------------------------------------------------------------------------- 1 | import BookingRepository from "./BookingRepository"; 2 | import RoomRepository from "./RoomRepository"; 3 | 4 | export default class GetBookingByCode { 5 | 6 | constructor (readonly roomRepository: RoomRepository, readonly bookingRepository: BookingRepository) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const booking = await this.bookingRepository.getBookingByCode(input.code); 11 | const room = await this.roomRepository.getById(booking.roomId); 12 | return { 13 | code: booking.code, 14 | category: room.category, 15 | duration: booking.duration, 16 | price: booking.price 17 | } 18 | } 19 | } 20 | 21 | type Input = { 22 | code: string 23 | } 24 | 25 | type Output = { 26 | code: string, 27 | category: string, 28 | duration: number, 29 | price: number 30 | } 31 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/ImportBooking.ts: -------------------------------------------------------------------------------- 1 | import Usecase from "./Usecase"; 2 | 3 | // ImportDecorator 4 | export default class ImportBooking implements Usecase { 5 | 6 | constructor (readonly usecase: Usecase) { 7 | } 8 | 9 | async execute (input: Input): Promise { 10 | const output: Output = { 11 | code: [] 12 | } 13 | const lines = input.file.split("\n"); 14 | for (const line of lines.slice(1)) { 15 | const [email, checkinDate, checkoutDate, category] = line.split(";"); 16 | const inputUsecase = { 17 | email, 18 | checkinDate: new Date(checkinDate), 19 | checkoutDate: new Date(checkoutDate), 20 | category 21 | }; 22 | const outputUsecase = await this.usecase.execute(inputUsecase); 23 | output.code.push(outputUsecase.code); 24 | } 25 | return output; 26 | } 27 | } 28 | 29 | type Input = { 30 | file: string 31 | } 32 | 33 | type Output = { 34 | code: string[] 35 | } 36 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/LogDecorator.ts: -------------------------------------------------------------------------------- 1 | import Usecase from "./Usecase"; 2 | 3 | export default class LogDecorator implements Usecase { 4 | 5 | constructor (readonly usecase: Usecase) { 6 | } 7 | 8 | async execute (input: any): Promise { 9 | console.log("log", input); 10 | return this.usecase.execute(input); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/Room.ts: -------------------------------------------------------------------------------- 1 | export default class Room { 2 | 3 | constructor (readonly roomId: number, readonly category: string, readonly price: number, readonly status: string) { 4 | } 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/RoomRepository.ts: -------------------------------------------------------------------------------- 1 | import Room from "./Room"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface RoomRepository { 5 | getAvailableRoomsByPeriodAndCategory (checkinDate: Date, checkoutDate: Date, category: string): Promise; 6 | getById (roomId: number): Promise; 7 | } 8 | 9 | export class RoomRepositoryDatabase implements RoomRepository { 10 | 11 | async getAvailableRoomsByPeriodAndCategory(checkinDate: Date, checkoutDate: Date, category: string): Promise { 12 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 13 | const roomsData = await connection.query("select * from design_patterns.room where category = $1 and status = 'available' and room_id not in (select room_id from design_patterns.booking where (checkin_date, checkout_date) overlaps ($2, $3) and status = 'confirmed')", [category, checkinDate, checkoutDate]); 14 | await connection.$pool.end(); 15 | const rooms: Room[] = []; 16 | for (const roomData of roomsData) { 17 | rooms.push(new Room(roomData.room_id, roomData.category, parseFloat(roomData.price), roomData.status)); 18 | } 19 | return rooms; 20 | } 21 | 22 | async getById(roomId: number): Promise { 23 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 24 | const [roomData] = await connection.query("select * from design_patterns.room where room_id = $1", [roomId]); 25 | await connection.$pool.end(); 26 | return new Room(roomData.room_id, roomData.category, parseFloat(roomData.price), roomData.status); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/SecurityDecorator.ts: -------------------------------------------------------------------------------- 1 | import Usecase from "./Usecase"; 2 | 3 | export default class LogDecorator implements Usecase { 4 | 5 | constructor (readonly usecase: Usecase) { 6 | } 7 | 8 | async execute (input: any): Promise { 9 | console.log("security"); 10 | return this.usecase.execute(input); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/Usecase.ts: -------------------------------------------------------------------------------- 1 | export default interface Usecase { 2 | execute (input: any): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /src/gof/structural/decorator/create.sql: -------------------------------------------------------------------------------- 1 | drop schema design_patterns cascade; 2 | create schema design_patterns; 3 | 4 | create table design_patterns.room ( 5 | room_id integer, 6 | category text, 7 | price numeric, 8 | status text 9 | ); 10 | 11 | create table design_patterns.booking ( 12 | code text, 13 | room_id integer, 14 | email text, 15 | checkin_date timestamp, 16 | checkout_date timestamp, 17 | duration integer, 18 | price numeric, 19 | status text 20 | ); 21 | 22 | insert into design_patterns.room (room_id, category, price, status) values (1, 'suite', 500, 'available'); 23 | insert into design_patterns.room (room_id, category, price, status) values (2, 'suite', 500, 'available'); 24 | insert into design_patterns.room (room_id, category, price, status) values (3, 'standard', 300, 'available'); 25 | insert into design_patterns.room (room_id, category, price, status) values (4, 'standard', 300, 'maintenance'); 26 | insert into design_patterns.room (room_id, category, price, status) values (5, 'suite', 500, 'available'); 27 | insert into design_patterns.room (room_id, category, price, status) values (6, 'suite', 500, 'available'); 28 | 29 | insert into design_patterns.booking values ('abc', 1, 'john.doe@gmail.com', '2021-03-10T10:00:00', '2021-03-12T10:00:00', 2, 1000, 'confirmed'); 30 | insert into design_patterns.booking values ('abc', 2, 'john.doe@gmail.com', '2021-03-10T10:00:00', '2021-03-12T10:00:00', 2, 1000, 'confirmed'); 31 | -------------------------------------------------------------------------------- /src/gof/structural/flyweight/main.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | export class LotteryTicket { 4 | 5 | constructor (readonly draw: DrawFlyweight, readonly bet1: string, readonly bet2: string, readonly bet3: string, readonly bet4: string, readonly bet5: string, readonly bet6: string) { 6 | } 7 | } 8 | 9 | export class DrawFlyweight { 10 | 11 | constructor (readonly draw: string, readonly date: Date) { 12 | } 13 | } 14 | 15 | export class FlyweightFactory { 16 | static cache: { [index: string]: DrawFlyweight } = {}; 17 | 18 | static getDrawFlyweight (draw: string, date: string) { 19 | const index = `${draw};${date}`; 20 | if (!FlyweightFactory.cache[index]) { 21 | FlyweightFactory.cache[index] = new DrawFlyweight(draw, new Date(date)); 22 | } 23 | return FlyweightFactory.cache[index]; 24 | } 25 | } 26 | 27 | const data = fs.readFileSync("./data/bets.csv", "utf8"); 28 | const tickets = []; 29 | for (const line of data.split("\n")) { 30 | const [draw, date, bet1, bet2, bet3, bet4, bet5, bet6] = line.split(";"); 31 | const ticket = new LotteryTicket(FlyweightFactory.getDrawFlyweight(draw, date), bet1, bet2, bet3, bet4, bet5, bet6); 32 | tickets.push(ticket); 33 | } 34 | console.log(process.memoryUsage().heapUsed/Math.pow(1024,2)); 35 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/ApplyForLoan.ts: -------------------------------------------------------------------------------- 1 | import InstallmentRepository from "./InstallmentRepository"; 2 | import LoanFactory from "./LoanFactory"; 3 | import LoanRepository from "./LoanRepository"; 4 | import RepositoryFactory from "./RepositoryFactory"; 5 | 6 | export default class ApplyForLoan { 7 | loanRepository: LoanRepository; 8 | installmentRepository: InstallmentRepository; 9 | 10 | constructor (readonly repositoryFactory: RepositoryFactory, readonly loanFactory: LoanFactory) { 11 | this.loanRepository = repositoryFactory.createLoanRepository(); 12 | this.installmentRepository = repositoryFactory.createInstallmentRepository(); 13 | } 14 | 15 | async execute (input: Input): Promise { 16 | const loan = this.loanFactory.createLoan(input.amount, input.income, input.installments); 17 | const installmentCalculator = this.loanFactory.createInstallmentCalculator(); 18 | const installments = installmentCalculator.calculate(loan); 19 | await this.loanRepository.save(loan); 20 | for (const installment of installments) { 21 | await this.installmentRepository.save(installment); 22 | } 23 | return { 24 | loanId: loan.loanId 25 | }; 26 | } 27 | } 28 | 29 | type Input = { 30 | amount: number, 31 | income: number, 32 | installments: number 33 | } 34 | 35 | type Output = { 36 | loanId: string 37 | } 38 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/GetLoan.ts: -------------------------------------------------------------------------------- 1 | import { SACInstallmentCalculator } from "./InstallmentCalculator"; 2 | import InstallmentRepository from "./InstallmentRepository"; 3 | import { MortgageLoan } from "./Loan" 4 | import LoanRepository from "./LoanRepository"; 5 | import RepositoryFactory from "./RepositoryFactory"; 6 | 7 | export default class GetLoan { 8 | loanRepository: LoanRepository; 9 | installmentRepository: InstallmentRepository; 10 | 11 | constructor (readonly repositoryFactory: RepositoryFactory) { 12 | this.loanRepository = repositoryFactory.createLoanRepository(); 13 | this.installmentRepository = repositoryFactory.createInstallmentRepository(); 14 | } 15 | 16 | async execute (input: Input): Promise { 17 | const loan = await this.loanRepository.getById(input.loanId); 18 | const installments = await this.installmentRepository.listByLoanId(input.loanId); 19 | const output: Output = { 20 | amount: loan.amount, 21 | income: loan.income, 22 | installments: [] 23 | } 24 | for (const installment of installments) { 25 | output.installments.push({ 26 | number: installment.number, 27 | amount: installment.amount, 28 | amortization: installment.amortization, 29 | interest: installment.interest, 30 | balance: installment.balance 31 | }); 32 | } 33 | return output; 34 | } 35 | } 36 | 37 | type Input = { 38 | loanId: string 39 | } 40 | 41 | type Output = { 42 | amount: number, 43 | income: number, 44 | installments: { number: number, amount: number, amortization: number, interest: number, balance: number }[] 45 | } 46 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/Installment.ts: -------------------------------------------------------------------------------- 1 | export default class Installment { 2 | 3 | constructor (readonly loanId: string, readonly number: number, readonly amount: number, readonly amortization: number, readonly interest: number, readonly balance: number) { 4 | 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/InstallmentCalculator.ts: -------------------------------------------------------------------------------- 1 | import currency from "currency.js"; 2 | import Installment from "./Installment"; 3 | import Loan from "./Loan"; 4 | 5 | export default interface InstallmentCalculator { 6 | calculate (loan: Loan): Installment[]; 7 | } 8 | 9 | export class SACInstallmentCalculator implements InstallmentCalculator { 10 | 11 | calculate(loan: Loan): Installment[] { 12 | const installments: Installment[] = []; 13 | let balance = currency(loan.amount); 14 | const rate = (loan.rate / 100) / 12; 15 | let installmentNumber = 1; 16 | let amortization = currency(balance.value / loan.installments); 17 | while (balance.value > 0.10) { 18 | let interest = currency(balance.value * rate); 19 | let updatedBalance = currency(balance.value + interest.value); 20 | let amount = currency(interest.value + amortization.value); 21 | balance = currency(updatedBalance.value - amount.value); 22 | if (balance.value < 0.10) balance = currency(0); 23 | installments.push(new Installment( 24 | loan.loanId, 25 | installmentNumber, 26 | amount.value, 27 | amortization.value, 28 | interest.value, 29 | balance.value 30 | )); 31 | installmentNumber++; 32 | } 33 | return installments; 34 | } 35 | 36 | } 37 | 38 | export class PriceInstallmentCalculator implements InstallmentCalculator { 39 | 40 | calculate(loan: Loan): Installment[] { 41 | const installments: Installment[] = []; 42 | let balance = currency(loan.amount); 43 | const rate = (loan.rate / 100) / 12; 44 | let installmentNumber = 1; 45 | const formula = Math.pow((1 + rate), loan.installments); 46 | let amount = balance.multiply((formula * rate) / (formula - 1)); 47 | while (balance.value > 2) { 48 | let interest = balance.multiply(rate); 49 | let amortization = amount.subtract(interest); 50 | balance = balance.subtract(amortization); 51 | if (balance.value < 2) balance = currency(0); 52 | installments.push(new Installment( 53 | loan.loanId, 54 | installmentNumber, 55 | amount.value, 56 | amortization.value, 57 | interest.value, 58 | balance.value 59 | )); 60 | installmentNumber++; 61 | } 62 | return installments; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/InstallmentRepository.ts: -------------------------------------------------------------------------------- 1 | import Installment from "./Installment"; 2 | import Loan from "./Loan"; 3 | 4 | export default interface InstallmentRepository { 5 | save (installment: Installment): Promise; 6 | listByLoanId (loanId: string): Promise; 7 | } 8 | 9 | export class InstallmentRepositoryMemory implements InstallmentRepository { 10 | installments: Installment[]; 11 | static instance: InstallmentRepository; 12 | 13 | private constructor () { 14 | this.installments = []; 15 | } 16 | 17 | async save(installment: Installment): Promise { 18 | this.installments.push(installment); 19 | } 20 | 21 | async listByLoanId(loanId: string): Promise { 22 | const installments = this.installments.filter((installment: Installment) => installment.loanId === loanId); 23 | return installments; 24 | } 25 | 26 | static getInstance () { 27 | if (!InstallmentRepositoryMemory.instance) { 28 | InstallmentRepositoryMemory.instance = new InstallmentRepositoryMemory(); 29 | } 30 | return InstallmentRepositoryMemory.instance; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/Loan.ts: -------------------------------------------------------------------------------- 1 | import crypto from "crypto"; 2 | 3 | export default abstract class Loan { 4 | abstract rate: number; 5 | 6 | constructor (readonly loanId: string, readonly amount: number, readonly income: number, readonly installments: number, readonly type: string) { 7 | } 8 | 9 | static create (amount: number, income: number, installments: number) { 10 | throw new Error("This method is abstract"); 11 | } 12 | } 13 | 14 | export class MortgageLoan extends Loan { 15 | rate = 10; 16 | 17 | constructor (loanId: string, amount: number, income: number, installments: number) { 18 | super(loanId, amount, income, installments, "mortgage"); 19 | if (installments > 420) throw new Error("The maximum number of installments for mortgage loan is 420"); 20 | if ((income * 0.25) < amount/installments) throw new Error("The installment amount could not exceed 25% of monthly income"); 21 | } 22 | 23 | static create (amount: number, income: number, installments: number) { 24 | const loanId = crypto.randomUUID(); 25 | return new MortgageLoan(loanId, amount, income, installments); 26 | } 27 | } 28 | 29 | export class CarLoan extends Loan { 30 | rate = 15; 31 | 32 | constructor (loanId: string, amount: number, income: number, installments: number) { 33 | super(loanId, amount, income, installments, "mortgage"); 34 | if (installments > 60) throw new Error("The maximum number of installments for car loan is 60"); 35 | if ((income * 0.30) < amount/installments) throw new Error("The installment amount could not exceed 30% of monthly income"); 36 | } 37 | 38 | static create (amount: number, income: number, installments: number) { 39 | const loanId = crypto.randomUUID(); 40 | return new CarLoan(loanId, amount, income, installments); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/LoanFactory.ts: -------------------------------------------------------------------------------- 1 | import InstallmentCalculator, { PriceInstallmentCalculator, SACInstallmentCalculator } from "./InstallmentCalculator"; 2 | import Loan, { CarLoan, MortgageLoan } from "./Loan"; 3 | 4 | export default interface LoanFactory { 5 | createLoan(amount: number, income: number, installments: number): Loan; 6 | createInstallmentCalculator(): InstallmentCalculator; 7 | } 8 | 9 | export class MortgageLoanFactory implements LoanFactory { 10 | 11 | createLoan(amount: number, income: number, installments: number): MortgageLoan { 12 | return MortgageLoan.create(amount, income, installments); 13 | } 14 | 15 | createInstallmentCalculator(): InstallmentCalculator { 16 | return new SACInstallmentCalculator(); 17 | } 18 | 19 | } 20 | 21 | export class CarLoanFactory implements LoanFactory { 22 | 23 | createLoan(amount: number, income: number, installments: number): MortgageLoan { 24 | return CarLoan.create(amount, income, installments); 25 | } 26 | 27 | createInstallmentCalculator(): InstallmentCalculator { 28 | return new PriceInstallmentCalculator(); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/LoanRepository.ts: -------------------------------------------------------------------------------- 1 | import Loan from "./Loan"; 2 | 3 | export default interface LoanRepository { 4 | save (loan: Loan): Promise; 5 | getById (loanId: string): Promise; 6 | } 7 | 8 | export class LoanRepositoryMemory implements LoanRepository { 9 | loans: Loan[]; 10 | static instance: LoanRepository; 11 | 12 | private constructor () { 13 | this.loans = []; 14 | } 15 | 16 | async save(loan: Loan): Promise { 17 | this.loans.push(loan); 18 | } 19 | 20 | async getById(loanId: string): Promise { 21 | const loan = this.loans.find((loan: Loan) => loan.loanId === loanId); 22 | if (!loan) throw new Error("Loan not found"); 23 | return loan; 24 | } 25 | 26 | static getInstance () { 27 | if (!LoanRepositoryMemory.instance) { 28 | LoanRepositoryMemory.instance = new LoanRepositoryMemory(); 29 | } 30 | return LoanRepositoryMemory.instance; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/poeaa/domain_model/RepositoryFactory.ts: -------------------------------------------------------------------------------- 1 | import InstallmentRepository, { InstallmentRepositoryMemory } from "./InstallmentRepository"; 2 | import LoanRepository, { LoanRepositoryMemory } from "./LoanRepository"; 3 | 4 | export default interface RepositoryFactory { 5 | createLoanRepository (): LoanRepository; 6 | createInstallmentRepository (): InstallmentRepository; 7 | } 8 | 9 | export class RepositoryMemoryFactory implements RepositoryFactory { 10 | 11 | createLoanRepository(): LoanRepository { 12 | return LoanRepositoryMemory.getInstance(); 13 | } 14 | 15 | createInstallmentRepository(): InstallmentRepository { 16 | return InstallmentRepositoryMemory.getInstance(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/poeaa/orm/AccountModel.ts: -------------------------------------------------------------------------------- 1 | import { Model, column, model } from "./ORM"; 2 | 3 | @model("cccat16", "account") 4 | export default class AccountModel extends Model { 5 | @column("account_id", true) 6 | accountId: string; 7 | @column("name") 8 | name: string; 9 | @column("email") 10 | email: string; 11 | @column("cpf") 12 | cpf: string; 13 | @column("car_plate") 14 | carPlate: string; 15 | 16 | constructor (accountId: string, name: string, email: string, cpf: string, carPlate: string) { 17 | super(); 18 | this.accountId = accountId; 19 | this.name = name; 20 | this.email = email; 21 | this.cpf = cpf; 22 | this.carPlate = carPlate; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/poeaa/orm/DatabaseConnection.ts: -------------------------------------------------------------------------------- 1 | import pgp from "pg-promise"; 2 | 3 | export default interface DatabaseConnection { 4 | query (statement: string, params: any): Promise; 5 | close (): Promise; 6 | } 7 | 8 | export class PgPromiseAdapter implements DatabaseConnection { 9 | connection: any; 10 | 11 | constructor () { 12 | this.connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 13 | } 14 | 15 | query(statement: string, params: any): Promise { 16 | return this.connection.query(statement, params); 17 | } 18 | 19 | async close(): Promise { 20 | // detalhe traduzido no adapter 21 | return this.connection.$pool.end(); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/poeaa/orm/ORM.ts: -------------------------------------------------------------------------------- 1 | import DatabaseConnection from "./DatabaseConnection"; 2 | 3 | export default class ORM { 4 | 5 | constructor (readonly databaseConnection: DatabaseConnection) { 6 | } 7 | 8 | async save (model: Model) { 9 | const columns = model.columns!.map((column: any) => column.column).join(","); 10 | const params = model.columns!.map((column: any, index: number) => `$${index + 1}`).join(","); 11 | const values = model.columns!.map((column: any) => model[column.property]); 12 | const query = `insert into ${model.schema}.${model.table} (${columns}) values (${params})`; 13 | await this.databaseConnection.query(query, values); 14 | } 15 | 16 | async get (field: string, value: string, model: any) { 17 | const query = `select * from ${model.prototype.schema}.${model.prototype.table} where ${field} = $1`; 18 | const [data] = await this.databaseConnection.query(query, [value]); 19 | console.log(data); 20 | const obj = new model(); 21 | for (const column of model.prototype.columns) { 22 | obj[column.property] = data[column.column]; 23 | } 24 | return obj; 25 | } 26 | } 27 | 28 | export abstract class Model { 29 | schema!: string; 30 | table!: string; 31 | columns!: { column: string, property: string, pk: boolean }[]; 32 | [property: string]: any; 33 | } 34 | 35 | export function model (schema: string, table: string) { 36 | return function (constructor: Function) { 37 | constructor.prototype.schema = schema; 38 | constructor.prototype.table = table; 39 | } 40 | } 41 | 42 | export function column (name: string, pk: boolean = false) { 43 | return function (target: any, propertyKey: string) { 44 | target.columns = target.columns || []; 45 | target.columns.push({ property: propertyKey, column: name, pk }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/poeaa/repository/Email.ts: -------------------------------------------------------------------------------- 1 | export default class Email { 2 | private value: string; 3 | 4 | constructor (email: string) { 5 | if (!email.match(/.+@.+.com/)) throw new Error("Invalid email"); 6 | this.value = email; 7 | } 8 | 9 | getValue () { 10 | return this.value; 11 | } 12 | } -------------------------------------------------------------------------------- /src/poeaa/repository/Password.ts: -------------------------------------------------------------------------------- 1 | export default class Password { 2 | private value: string; 3 | 4 | constructor (password: string) { 5 | if (password.length < 8) throw new Error("Minimum length is 8"); 6 | this.value = password; 7 | } 8 | 9 | getValue () { 10 | return this.value; 11 | } 12 | } -------------------------------------------------------------------------------- /src/poeaa/repository/User.ts: -------------------------------------------------------------------------------- 1 | import Email from "./Email"; 2 | import Password from "./Password"; 3 | 4 | export default class User { 5 | private status: "active" | "blocked"; 6 | private password: Password; 7 | private email: Email; 8 | 9 | constructor (private name: string, email: string, password: string) { 10 | this.password = new Password(password); 11 | this.email = new Email(email); 12 | this.status = "active"; 13 | } 14 | 15 | updatePassword (password: string) { 16 | this.password = new Password(password); 17 | } 18 | 19 | updateEmail (email: string) { 20 | this.email = new Email(email); 21 | } 22 | 23 | block () { 24 | if (this.status === "blocked") throw new Error("User is already blocked"); 25 | this.status = "blocked"; 26 | } 27 | 28 | getName () { 29 | return this.name; 30 | } 31 | 32 | getEmail () { 33 | return this.email.getValue(); 34 | } 35 | 36 | getPassword () { 37 | return this.password.getValue(); 38 | } 39 | 40 | getStatus () { 41 | return this.status; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/poeaa/repository/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import User from "./User"; 2 | import pgp from "pg-promise"; 3 | 4 | export default interface UserRepository { 5 | save (user: User): Promise; 6 | update (user: User): Promise; 7 | delete (email: string): Promise; 8 | list (): Promise; 9 | getByEmail (email: string): Promise; 10 | } 11 | 12 | export class UserRepositoryDatabase implements UserRepository { 13 | 14 | async save(user: User): Promise { 15 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 16 | await connection.query("insert into design_patterns.user (name, email, password, status) values ($1, $2, $3, $4)", [user.getName(), user.getEmail(), user.getPassword(), user.getStatus()]); 17 | await connection.$pool.end(); 18 | } 19 | 20 | async update(user: User): Promise { 21 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 22 | await connection.query("update design_patterns.user set name = $1, password = $2, status = $3 where email = $4", [user.getName(), user.getPassword(), user.getStatus(), user.getEmail()]); 23 | await connection.$pool.end(); 24 | } 25 | 26 | async delete(email: string): Promise { 27 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 28 | await connection.query("delete from design_patterns.user where email = $1", [email]); 29 | await connection.$pool.end(); 30 | } 31 | 32 | async list(): Promise { 33 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 34 | const usersData = await connection.query("select * from design_patterns.user", []); 35 | await connection.$pool.end(); 36 | const users = []; 37 | for (const userData of usersData) { 38 | const user = new User(userData.name, userData.email, userData.password); 39 | users.push(user); 40 | } 41 | return users; 42 | } 43 | 44 | async getByEmail(email: string): Promise { 45 | const connection = pgp()("postgres://postgres:123456@localhost:5432/app"); 46 | const [userData] = await connection.query("select * from design_patterns.user where email = $1", [email]); 47 | if (!userData) throw new Error("User not found"); 48 | const user = new User(userData.name, userData.email, userData.password); 49 | await connection.$pool.end(); 50 | return user; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/poeaa/repository/create.sql: -------------------------------------------------------------------------------- 1 | drop schema design_patterns cascade; 2 | 3 | create schema design_patterns; 4 | 5 | create table design_patterns.user ( 6 | name text, 7 | email text, 8 | password text, 9 | status text 10 | ); 11 | -------------------------------------------------------------------------------- /src/poeaa/transaction_script/controller/LoanController.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import LoanService from "../service/LoanService"; 3 | const router = express.Router(); 4 | 5 | router.post("/apply_for_loan", async function (req: any, res: any) { 6 | // type: string, installments: number, income: number, amount: number 7 | const loanService = new LoanService(); 8 | await loanService.applyForLoan(req.body); 9 | res.end(); 10 | }); 11 | 12 | export default router; 13 | -------------------------------------------------------------------------------- /src/poeaa/transaction_script/dao/InstallmentDAO.ts: -------------------------------------------------------------------------------- 1 | export async function saveInstallment (installment: any): Promise { 2 | console.log(installment); 3 | } 4 | -------------------------------------------------------------------------------- /src/poeaa/transaction_script/dao/LoanDAO.ts: -------------------------------------------------------------------------------- 1 | export async function saveLoan (loan: any): Promise { 2 | console.log(loan); 3 | } -------------------------------------------------------------------------------- /src/poeaa/transaction_script/main.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import loanController from "./controller/LoanController"; 3 | const app = express(); 4 | app.use(express.json()); 5 | app.use(loanController); 6 | app.listen(3000); -------------------------------------------------------------------------------- /src/poeaa/transaction_script/service/InstallmentService.ts: -------------------------------------------------------------------------------- 1 | import currency from "currency.js"; 2 | import { saveInstallment } from "../dao/InstallmentDAO"; 3 | 4 | export async function saveInstallmentsUsingSAC(loan: any) { 5 | let balance = currency(loan.amount); 6 | const rate = (loan.rate / 100) / 12; 7 | let installmentNumber = 1; 8 | let amortization = currency(balance.value / loan.installments); 9 | while (balance.value > 0.10) { 10 | let interest = currency(balance.value * rate); 11 | let updatedBalance = currency(balance.value + interest.value); 12 | let amount = currency(interest.value + amortization.value); 13 | balance = currency(updatedBalance.value - amount.value); 14 | if (balance.value < 0.10) balance = currency(0); 15 | saveInstallment({ 16 | loanId: loan.loanId, 17 | installmentNumber, 18 | amount: amount.value, 19 | amortization: amortization.value, 20 | interest: interest.value, 21 | balance: balance.value 22 | }); 23 | installmentNumber++; 24 | } 25 | } 26 | 27 | export async function saveInstallmentsUsingPrice(loan: any) { 28 | let balance = currency(loan.amount); 29 | const rate = (loan.rate / 100) / 12; 30 | let installmentNumber = 1; 31 | const formula = Math.pow((1 + rate), loan.installments); 32 | let installmentAmount = balance.multiply((formula * rate) / (formula - 1)); 33 | while (balance.value > 2) { 34 | let interest = balance.multiply(rate); 35 | let amortization = installmentAmount.subtract(interest); 36 | balance = balance.subtract(amortization); 37 | if (balance.value < 2) balance = currency(0); 38 | saveInstallment({ 39 | loanId: loan.loanId, 40 | installmentNumber, 41 | amount: installmentAmount.value, 42 | amortization: amortization.value, 43 | interest: interest.value, 44 | balance: balance.value 45 | }); 46 | installmentNumber++; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/poeaa/transaction_script/service/Loan.ts: -------------------------------------------------------------------------------- 1 | // struc (C), dicionário, tabela... 2 | 3 | export default class Loan { 4 | 5 | constructor ( 6 | public loanId: string, 7 | readonly type: string, 8 | readonly installments: number, 9 | readonly income: number, 10 | readonly amount: number, 11 | public rate: number 12 | ) { 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/poeaa/transaction_script/service/LoanService.ts: -------------------------------------------------------------------------------- 1 | import { saveLoan } from "../dao/LoanDAO"; 2 | import { saveInstallmentsUsingSAC, saveInstallmentsUsingPrice } from "./InstallmentService"; 3 | import Loan from "./Loan"; 4 | 5 | export default class LoanService { 6 | 7 | async applyForLoan (loan: Loan) { 8 | loan.loanId = crypto.randomUUID(); 9 | if (loan.type === "mortgage") { 10 | if (loan.installments > 420) throw new Error("The maximum number of installments for mortgage loan is 420"); 11 | if ((loan.income * 0.25) < loan.amount/loan.installments) throw new Error("The installment amount could not exceed 25% of monthly income"); 12 | loan.rate = 10; 13 | await saveInstallmentsUsingSAC(loan); 14 | } 15 | if (loan.type === "car") { 16 | if (loan.installments > 60) throw new Error("The maximum number of installments for car loan is 60"); 17 | if ((loan.income * 0.30) < loan.amount/loan.installments) throw new Error("The installment amount could not exceed 30% of monthly income"); 18 | loan.rate = 15; 19 | await saveInstallmentsUsingPrice(loan); 20 | } 21 | await saveLoan(loan); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test/gof/behavioral/chain_of_responsibility/Ride.test.ts: -------------------------------------------------------------------------------- 1 | import { NormalFareCalculator, OvernightFareCalculator, OvernightSundayFareCalculator, SundayFareCalculator } from "../../../../src/gof/behavioral/chain_of_responsibility/FareCalculator"; 2 | import Ride from "../../../../src/gof/behavioral/chain_of_responsibility/Ride"; 3 | 4 | let ride: Ride; 5 | 6 | beforeEach(() => { 7 | const overnightSundayFareCalculator = new OvernightSundayFareCalculator(); // 4 8 | const sundayFareCalculator = new SundayFareCalculator(overnightSundayFareCalculator); // 3 9 | const overnightFareCalculator = new OvernightFareCalculator(sundayFareCalculator); // 2 10 | const normalFareCalculator = new NormalFareCalculator(overnightFareCalculator); // 1 11 | ride = new Ride(normalFareCalculator); 12 | }); 13 | 14 | test("Deve calcular o valor da corrida no horário normal", function () { 15 | ride.addSegment(10, new Date("2021-03-01T10:00:00")); 16 | ride.calculateFare(); 17 | expect(ride.getFare()).toBe(21); 18 | }); 19 | 20 | test("Deve calcular o valor da corrida no horário noturno", function () { 21 | ride.addSegment(10, new Date("2021-03-01T23:00:00")); 22 | ride.calculateFare(); 23 | expect(ride.getFare()).toBe(39); 24 | }); 25 | 26 | test("Deve calcular o valor da corrida no horário de domingo", function () { 27 | ride.addSegment(10, new Date("2021-03-07T10:00:00")); 28 | ride.calculateFare(); 29 | expect(ride.getFare()).toBe(29); 30 | }); 31 | 32 | test("Deve calcular o valor da corrida no horário de domingo de noite", function () { 33 | ride.addSegment(10, new Date("2021-03-07T23:00:00")); 34 | ride.calculateFare(); 35 | expect(ride.getFare()).toBe(50); 36 | }); 37 | 38 | test("Não deve calcular o valor da corrida se a distância for inválida", function () { 39 | expect(() => ride.addSegment(-10, new Date("2021-03-01T10:00:00"))).toThrow(new Error("Invalid distance")); 40 | }); 41 | -------------------------------------------------------------------------------- /test/gof/behavioral/chain_of_responsibility/calculateRide.test.ts: -------------------------------------------------------------------------------- 1 | import { calculateFare } from "../../../../src/gof/behavioral/chain_of_responsibility/calculateRide"; 2 | 3 | test("Deve calcular o valor da corrida no horário normal", function () { 4 | const segments = [ 5 | { distance: 10, date: new Date("2021-03-01T10:00:00") } 6 | ]; 7 | const fare = calculateFare(segments); 8 | expect(fare).toBe(21); 9 | }); 10 | 11 | test("Deve calcular o valor da corrida no horário noturno", function () { 12 | const segments = [ 13 | { distance: 10, date: new Date("2021-03-01T23:00:00") } 14 | ]; 15 | const fare = calculateFare(segments); 16 | expect(fare).toBe(39); 17 | }); 18 | 19 | test("Deve calcular o valor da corrida no horário de domingo", function () { 20 | const segments = [ 21 | { distance: 10, date: new Date("2021-03-07T10:00:00") } 22 | ]; 23 | const fare = calculateFare(segments); 24 | expect(fare).toBe(29); 25 | }); 26 | 27 | test("Deve calcular o valor da corrida no horário de domingo a noite", function () { 28 | const segments = [ 29 | { distance: 10, date: new Date("2021-03-07T23:00:00") } 30 | ]; 31 | const fare = calculateFare(segments); 32 | expect(fare).toBe(50); 33 | }); 34 | 35 | test("Deve calcular o valor da corrida com tarifa mínima", function () { 36 | const segments = [ 37 | { distance: 2, date: new Date("2021-03-01T10:00:00") } 38 | ]; 39 | const fare = calculateFare(segments); 40 | expect(fare).toBe(10); 41 | }); 42 | 43 | test("Não deve calcular o valor da corrida se a distância for inválida", function () { 44 | const segments = [ 45 | { distance: null, date: new Date("2021-03-01T10:00:00") } 46 | ]; 47 | expect(() => calculateFare(segments)).toThrow(new Error("Invalid distance")); 48 | }); 49 | 50 | test("Não deve calcular o valor da corrida se a data for inválida", function () { 51 | const segments = [ 52 | { distance: 10, date: new Date("javascript") } 53 | ]; 54 | expect(() => calculateFare(segments)).toThrow(new Error("Invalid date")); 55 | }); 56 | -------------------------------------------------------------------------------- /test/gof/behavioral/command/BankAccount.test.ts: -------------------------------------------------------------------------------- 1 | import BankAccount from "../../../../src/gof/behavioral/command/BankAccount"; 2 | import TransferCommand from "../../../../src/gof/behavioral/command/TransferCommand"; 3 | 4 | test("Deve fazer uma transferência entre duas contas", function () { 5 | const from = new BankAccount(1); 6 | const to = new BankAccount(2); 7 | expect(from.getBalance()).toBe(0); 8 | expect(to.getBalance()).toBe(0); 9 | from.debit(100); 10 | to.credit(100); 11 | expect(from.getBalance()).toBe(-100); 12 | expect(to.getBalance()).toBe(100); 13 | }); 14 | 15 | test("Deve fazer uma transferência entre duas contas usando um comando", function () { 16 | const from = new BankAccount(1); 17 | const to = new BankAccount(2); 18 | expect(from.getBalance()).toBe(0); 19 | expect(to.getBalance()).toBe(0); 20 | const transferCommand = new TransferCommand(from, to, 100); 21 | transferCommand.execute(); 22 | expect(from.getBalance()).toBe(-100); 23 | expect(to.getBalance()).toBe(100); 24 | }); 25 | -------------------------------------------------------------------------------- /test/gof/behavioral/command/MakeTransfer.test.ts: -------------------------------------------------------------------------------- 1 | import BankAccount from "../../../../src/gof/behavioral/command/BankAccount"; 2 | import { BankAccountRepositoryMemory } from "../../../../src/gof/behavioral/command/BankAccountRepository"; 3 | import GetBalance from "../../../../src/gof/behavioral/command/GetBalance"; 4 | import MakeTransfer from "../../../../src/gof/behavioral/command/MakeTransfer"; 5 | 6 | test("Deve fazer uma transferência bancária", async function () { 7 | const bankAccountRepository = new BankAccountRepositoryMemory(); 8 | bankAccountRepository.save(new BankAccount(1)); 9 | bankAccountRepository.save(new BankAccount(2)); 10 | const makeTransfer = new MakeTransfer(bankAccountRepository); 11 | const input = { 12 | fromBankAccountId: 1, 13 | toBankAccountId: 2, 14 | amount: 100 15 | }; 16 | await makeTransfer.execute(input); 17 | const getBalance = new GetBalance(bankAccountRepository); 18 | const outputA = await getBalance.execute(1); 19 | expect(outputA.balance).toBe(-100); 20 | const outputB = await getBalance.execute(2); 21 | expect(outputB.balance).toBe(100); 22 | }); 23 | -------------------------------------------------------------------------------- /test/gof/behavioral/iterator/Wallet.test.ts: -------------------------------------------------------------------------------- 1 | import State from "../../../../src/gof/behavioral/iterator/State"; 2 | import UTXO from "../../../../src/gof/behavioral/iterator/UTXO"; 3 | import Wallet from "../../../../src/gof/behavioral/iterator/Wallet"; 4 | 5 | test("Deve transferir de uma carteira para a outra", function () { 6 | const wallet1 = new Wallet("alice"); 7 | const wallet2 = new Wallet("bob"); 8 | wallet1.addUTXO(new UTXO(10)); 9 | wallet1.addUTXO(new UTXO(2)); 10 | wallet2.addUTXO(new UTXO(1)); 11 | wallet2.addUTXO(new UTXO(1)); 12 | const state = new State(); 13 | state.addWallet(wallet1); 14 | state.addWallet(wallet2); 15 | expect(wallet1.getBalance()).toBe(12); 16 | expect(wallet2.getBalance()).toBe(2); 17 | state.transfer("alice", "bob", 5); 18 | expect(wallet1.getBalance()).toBe(7); 19 | expect(wallet1.utxos[0].amount).toBe(2); 20 | expect(wallet1.utxos[1].amount).toBe(5); 21 | expect(wallet2.getBalance()).toBe(7); 22 | expect(wallet2.utxos[0].amount).toBe(1); 23 | expect(wallet2.utxos[1].amount).toBe(1); 24 | expect(wallet2.utxos[2].amount).toBe(5); 25 | expect(() => state.transfer("alice", "bob", 10)).toThrow(new Error("Insuficient funds")); 26 | }); 27 | -------------------------------------------------------------------------------- /test/gof/behavioral/mediator/SaveGrade.test.ts: -------------------------------------------------------------------------------- 1 | import { AverageRepositoryDatabase } from "../../../../src/gof/behavioral/mediator/AverageRepository"; 2 | import CalculateAverage from "../../../../src/gof/behavioral/mediator/CalculateAverage"; 3 | import GetAverage from "../../../../src/gof/behavioral/mediator/GetAverage"; 4 | import { GradeRepositoryDatabase } from "../../../../src/gof/behavioral/mediator/GradeRepository"; 5 | import Mediator from "../../../../src/gof/behavioral/mediator/Mediator"; 6 | import SaveGrade from "../../../../src/gof/behavioral/mediator/SaveGrade"; 7 | import SaveGradeMediator from "../../../../src/gof/behavioral/mediator/SaveGradeMediator"; 8 | 9 | test("Deve salvar a nota do aluno e calcular a média", async function () { 10 | const studentId = Math.round(Math.random() * 1000000); 11 | const gradeRepository = new GradeRepositoryDatabase(); 12 | const averageRepository = new AverageRepositoryDatabase(); 13 | const calculateAverage = new CalculateAverage(gradeRepository, averageRepository); 14 | const saveGrade = new SaveGrade(gradeRepository, calculateAverage); 15 | const inputP1 = { 16 | studentId, 17 | exam: "P1", 18 | value: 10 19 | }; 20 | await saveGrade.execute(inputP1); 21 | const inputP2 = { 22 | studentId, 23 | exam: "P2", 24 | value: 9 25 | }; 26 | await saveGrade.execute(inputP2); 27 | const inputP3 = { 28 | studentId, 29 | exam: "P3", 30 | value: 8 31 | }; 32 | await saveGrade.execute(inputP3); 33 | const getAverage = new GetAverage(averageRepository); 34 | const output = await getAverage.execute(studentId); 35 | expect(output.average).toBe(9); 36 | }); 37 | 38 | test("Deve salvar a nota do aluno e calcular a média usando mediator", async function () { 39 | const studentId = Math.round(Math.random() * 1000000); 40 | const gradeRepository = new GradeRepositoryDatabase(); 41 | const averageRepository = new AverageRepositoryDatabase(); 42 | const calculateAverage = new CalculateAverage(gradeRepository, averageRepository); 43 | const mediator = new Mediator(); 44 | mediator.register("gradeSaved", async function (data: any) { 45 | await calculateAverage.execute(data.studentId); 46 | }); 47 | const saveGrade = new SaveGradeMediator(gradeRepository, mediator); 48 | const inputP1 = { 49 | studentId, 50 | exam: "P1", 51 | value: 10 52 | }; 53 | await saveGrade.execute(inputP1); 54 | const inputP2 = { 55 | studentId, 56 | exam: "P2", 57 | value: 9 58 | }; 59 | await saveGrade.execute(inputP2); 60 | const inputP3 = { 61 | studentId, 62 | exam: "P3", 63 | value: 8 64 | }; 65 | await saveGrade.execute(inputP3); 66 | const getAverage = new GetAverage(averageRepository); 67 | const output = await getAverage.execute(studentId); 68 | expect(output.average).toBe(9); 69 | }); 70 | -------------------------------------------------------------------------------- /test/gof/behavioral/state/Ticket.test.ts: -------------------------------------------------------------------------------- 1 | import Ticket from "../../../../src/gof/behavioral/state/Ticket"; 2 | 3 | test("Deve realizar transições de estado em um chamado", function () { 4 | const customerId = 1; 5 | const ticket = new Ticket(customerId, new Date("2021-03-01T08:00:00")); 6 | expect(ticket.getStatus()).toBe("requested"); 7 | expect(ticket.getStatistics(new Date("2021-03-01T09:00:00")).requestDuration).toBe(1); 8 | const employeeId = 2; 9 | ticket.assign(employeeId, new Date("2021-03-01T10:00:00")); 10 | expect(ticket.getStatistics(new Date("2021-03-01T11:00:00")).assignDuration).toBe(1); 11 | expect(ticket.getStatus()).toBe("assigned"); 12 | ticket.start(new Date("2021-03-01T16:00:00")); 13 | expect(ticket.getStatus()).toBe("in_progress"); 14 | ticket.close(new Date("2021-03-01T18:00:00")); 15 | expect(ticket.getStatus()).toBe("closed"); 16 | }); 17 | -------------------------------------------------------------------------------- /test/gof/behavioral/strategy/Checkout.test.ts: -------------------------------------------------------------------------------- 1 | import Checkin from "../../../../src/gof/behavioral/strategy/Checkin"; 2 | import Checkout from "../../../../src/gof/behavioral/strategy/Checkout"; 3 | import { ParkingTicketRepositoryDatabase } from "../../../../src/gof/behavioral/strategy/ParkingTicketRepository"; 4 | 5 | test("Deve calcular a tarifa do veículo estacionado no aeroporto", async function () { 6 | const plate = "AAA" + `${Math.random() * 1000}`.padStart(4, "0"); 7 | const parkingTicketRepository = new ParkingTicketRepositoryDatabase(); 8 | const checkin = new Checkin(parkingTicketRepository); 9 | const inputCheckin = { 10 | plate, 11 | checkinDate: new Date("2023-03-01T10:00:00"), 12 | location: "airport" 13 | } 14 | await checkin.execute(inputCheckin); 15 | const checkout = new Checkout(parkingTicketRepository); 16 | const inputCheckout = { 17 | plate, 18 | checkoutDate: new Date("2023-03-01T12:00:00") 19 | } 20 | const output = await checkout.execute(inputCheckout); 21 | expect(output.fare).toBe(20); 22 | }); 23 | 24 | test("Deve calcular a tarifa do veículo estacionado no shopping", async function () { 25 | const plate = "AAA" + `${Math.random() * 1000}`.padStart(4, "0"); 26 | const parkingTicketRepository = new ParkingTicketRepositoryDatabase(); 27 | const checkin = new Checkin(parkingTicketRepository); 28 | const inputCheckin = { 29 | plate, 30 | checkinDate: new Date("2023-03-01T10:00:00"), 31 | location: "shopping" 32 | } 33 | await checkin.execute(inputCheckin); 34 | const checkout = new Checkout(parkingTicketRepository); 35 | const inputCheckout = { 36 | plate, 37 | checkoutDate: new Date("2023-03-01T15:00:00") 38 | } 39 | const output = await checkout.execute(inputCheckout); 40 | expect(output.fare).toBe(30); 41 | }); 42 | 43 | test("Deve calcular a tarifa do veículo estacionado na praia", async function () { 44 | const plate = "AAA" + `${Math.random() * 1000}`.padStart(4, "0"); 45 | const parkingTicketRepository = new ParkingTicketRepositoryDatabase(); 46 | const checkin = new Checkin(parkingTicketRepository); 47 | const inputCheckin = { 48 | plate, 49 | checkinDate: new Date("2023-03-01T10:00:00"), 50 | location: "beach" 51 | } 52 | await checkin.execute(inputCheckin); 53 | const checkout = new Checkout(parkingTicketRepository); 54 | const inputCheckout = { 55 | plate, 56 | checkoutDate: new Date("2023-03-01T17:00:00") 57 | } 58 | const output = await checkout.execute(inputCheckout); 59 | expect(output.fare).toBe(10); 60 | }); 61 | 62 | test("Deve calcular a tarifa do veículo estacionado na rua", async function () { 63 | const plate = "AAA" + `${Math.random() * 1000}`.padStart(4, "0"); 64 | const parkingTicketRepository = new ParkingTicketRepositoryDatabase(); 65 | const checkin = new Checkin(parkingTicketRepository); 66 | const inputCheckin = { 67 | plate, 68 | checkinDate: new Date("2023-03-01T10:00:00"), 69 | location: "public" 70 | } 71 | await checkin.execute(inputCheckin); 72 | const checkout = new Checkout(parkingTicketRepository); 73 | const inputCheckout = { 74 | plate, 75 | checkoutDate: new Date("2023-03-01T12:00:00") 76 | } 77 | const output = await checkout.execute(inputCheckout); 78 | expect(output.fare).toBe(0); 79 | }); 80 | -------------------------------------------------------------------------------- /test/gof/behavioral/strategy/ParkingTicket.test.ts: -------------------------------------------------------------------------------- 1 | import ParkingTicket from "../../../../src/gof/behavioral/strategy/ParkingTicket"; 2 | 3 | test("Deve calcular a tarifa do veículo estacionado no aeroporto", async function () { 4 | const parkingTicket = new ParkingTicket("AAA9999", new Date("2023-03-01T10:00:00"), "airport"); 5 | parkingTicket.checkout(new Date("2023-03-01T12:00:00")); 6 | expect(parkingTicket.fare).toBe(20); 7 | }); 8 | -------------------------------------------------------------------------------- /test/gof/creational/abstract_factory/ApplyForLoan.test.ts: -------------------------------------------------------------------------------- 1 | import ApplyForLoan from "../../../../src/gof/creational/abstract_factory/ApplyForLoan"; 2 | import GetLoan from "../../../../src/gof/creational/abstract_factory/GetLoan"; 3 | import { InstallmentRepositoryMemory } from "../../../../src/gof/creational/abstract_factory/InstallmentRepository"; 4 | import { MortgageLoanFactory } from "../../../../src/gof/creational/abstract_factory/LoanFactory"; 5 | import { LoanRepositoryMemory } from "../../../../src/gof/creational/abstract_factory/LoanRepository"; 6 | import { RepositoryMemoryFactory } from "../../../../src/gof/creational/abstract_factory/RepositoryFactory"; 7 | 8 | test("Deve solicitar um financiamento imobiliário", async function () { 9 | const repositoryFactory = new RepositoryMemoryFactory(); 10 | const loanFactory = new MortgageLoanFactory(); 11 | const applyForLoan = new ApplyForLoan(repositoryFactory, loanFactory); 12 | const input = { 13 | amount: 100000, 14 | income: 10000, 15 | installments: 240 16 | }; 17 | const outputApplyForLoan = await applyForLoan.execute(input); 18 | const getLoan = new GetLoan(repositoryFactory); 19 | const outputGetLoan = await getLoan.execute(outputApplyForLoan); 20 | expect(outputGetLoan.amount).toBe(100000); 21 | expect(outputGetLoan.income).toBe(10000); 22 | expect(outputGetLoan.installments).toHaveLength(240); 23 | expect(outputGetLoan.installments.at(0)?.number).toBe(1); 24 | expect(outputGetLoan.installments.at(0)?.amount).toBe(1250); 25 | expect(outputGetLoan.installments.at(0)?.amortization).toBe(416.67); 26 | expect(outputGetLoan.installments.at(0)?.interest).toBe(833.33); 27 | expect(outputGetLoan.installments.at(0)?.balance).toBe(99583.33); 28 | expect(outputGetLoan.installments.at(239)?.number).toBe(240); 29 | expect(outputGetLoan.installments.at(239)?.amount).toBe(420.14); 30 | expect(outputGetLoan.installments.at(239)?.amortization).toBe(416.67); 31 | expect(outputGetLoan.installments.at(239)?.interest).toBe(3.47); 32 | expect(outputGetLoan.installments.at(239)?.balance).toBe(0); 33 | }); 34 | -------------------------------------------------------------------------------- /test/gof/creational/abstract_factory/InstallmentCalculator.test.ts: -------------------------------------------------------------------------------- 1 | import { PriceInstallmentCalculator, SACInstallmentCalculator } from "../../../../src/gof/creational/abstract_factory/InstallmentCalculator"; 2 | import { MortgageLoan } from "../../../../src/gof/creational/abstract_factory/Loan"; 3 | 4 | test("Deve calcular as parcelas utilizando SAC", function () { 5 | const installmentCalculator = new SACInstallmentCalculator(); 6 | const loan = MortgageLoan.create(100000, 10000, 240); 7 | const installments = installmentCalculator.calculate(loan); 8 | expect(installments).toHaveLength(240); 9 | expect(installments.at(0)?.number).toBe(1); 10 | expect(installments.at(0)?.amount).toBe(1250); 11 | expect(installments.at(0)?.amortization).toBe(416.67); 12 | expect(installments.at(0)?.interest).toBe(833.33); 13 | expect(installments.at(0)?.balance).toBe(99583.33); 14 | expect(installments.at(239)?.number).toBe(240); 15 | expect(installments.at(239)?.amount).toBe(420.14); 16 | expect(installments.at(239)?.amortization).toBe(416.67); 17 | expect(installments.at(239)?.interest).toBe(3.47); 18 | expect(installments.at(239)?.balance).toBe(0); 19 | }); 20 | 21 | test("Deve calcular as parcelas utilizando Price", function () { 22 | const installmentCalculator = new PriceInstallmentCalculator(); 23 | const loan = MortgageLoan.create(100000, 10000, 240); 24 | const installments = installmentCalculator.calculate(loan); 25 | expect(installments).toHaveLength(240); 26 | expect(installments.at(0)?.number).toBe(1); 27 | expect(installments.at(0)?.amount).toBe(965.02); 28 | expect(installments.at(0)?.amortization).toBe(131.69); 29 | expect(installments.at(0)?.interest).toBe(833.33); 30 | expect(installments.at(0)?.balance).toBe(99868.31); 31 | expect(installments.at(239)?.number).toBe(240); 32 | expect(installments.at(239)?.amount).toBe(965.02); 33 | expect(installments.at(239)?.amortization).toBe(957.03); 34 | expect(installments.at(239)?.interest).toBe(7.99); 35 | expect(installments.at(239)?.balance).toBe(0); 36 | }); 37 | -------------------------------------------------------------------------------- /test/gof/creational/abstract_factory/Loan.test.ts: -------------------------------------------------------------------------------- 1 | import { CarLoan, MortgageLoan } from "../../../../src/gof/creational/abstract_factory/Loan"; 2 | 3 | test("Deve criar um financiamento imobiliário", function () { 4 | const loan = MortgageLoan.create(100000, 10000, 240); 5 | expect(loan.loanId).toBeDefined(); 6 | expect(loan.amount).toBe(100000); 7 | expect(loan.income).toBe(10000); 8 | expect(loan.installments).toBe(240); 9 | }); 10 | 11 | test("Não deve criar um financiamento imobiliário com prazo superior a 420 meses", function () { 12 | expect(() => MortgageLoan.create(100000, 10000, 450)).toThrow(new Error("The maximum number of installments for mortgage loan is 420")); 13 | }); 14 | 15 | test("Não deve criar um financiamento imobiliário caso a parcela ocupa um valor superior a 25% da renda mensal", function () { 16 | expect(() => MortgageLoan.create(200000, 1000, 420)).toThrow(new Error("The installment amount could not exceed 25% of monthly income")); 17 | }); 18 | 19 | test("Não deve criar um financiamento veicular com prazo superior a 60 meses", function () { 20 | expect(() => CarLoan.create(100000, 10000, 72)).toThrow(new Error("The maximum number of installments for car loan is 60")); 21 | }); 22 | 23 | test("Não deve criar um financiamento veicular caso a parcela ocupa um valor superior a 30% da renda mensal", function () { 24 | expect(() => CarLoan.create(200000, 1000, 60)).toThrow(new Error("The installment amount could not exceed 30% of monthly income")); 25 | }); 26 | -------------------------------------------------------------------------------- /test/gof/creational/builder/FlightTicket.test.ts: -------------------------------------------------------------------------------- 1 | import FlightTicket from "../../../../src/gof/creational/builder/FlightTicket"; 2 | import FlightTicketBuilder from "../../../../src/gof/creational/builder/FlightTicketBuilder"; 3 | 4 | test("Deve criar uma passagem aérea", function () { 5 | const builder = new FlightTicketBuilder() 6 | .setFlight("Azul", "9876") 7 | .setTrip("FLN", "GRU") 8 | .setPassenger("John Doe", "john.doe@gmail.com", "111.111.111-11", "M") 9 | .setEmergencyContact("Bob Simpson", "551199999999") 10 | .setSeat("8A") 11 | .setCheckedBags(2) 12 | .setCheckinInformation(true, "1", "4A") 13 | .setPriority(5); 14 | const flightTicket = new FlightTicket(builder); 15 | expect(flightTicket.flightCode).toBe("9876"); 16 | expect(flightTicket.passengerName).toBe("John Doe"); 17 | }); 18 | -------------------------------------------------------------------------------- /test/gof/creational/factory_method/Ride.test.ts: -------------------------------------------------------------------------------- 1 | 2 | import Location from "../../../../src/gof/creational/factory_method/Location"; 3 | import Ride, { DistanceRide, TimeRide } from "../../../../src/gof/creational/factory_method/Ride"; 4 | import Segment, { DistanceSegment, TimeSegment } from "../../../../src/gof/creational/factory_method/Segment"; 5 | 6 | test("Deve criar e calcular a tarifa de uma corrida por distância", function () { 7 | const ride = DistanceRide.create(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 8 | const lastLocation = new Location(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 9 | const newLocation = new Location(-27.496887588317275, -48.522234807851476, new Date("2021-03-01T12:00:00")); 10 | const segment = new DistanceSegment(ride.rideId, lastLocation, newLocation); 11 | ride.updateLocation(new Location(-27.496887588317275, -48.522234807851476, new Date("2021-03-01T12:00:00"))); 12 | const fare = ride.calculateFare([segment]); 13 | expect(fare).toBe(40); 14 | }); 15 | 16 | test("Deve criar e calcular a tarifa de uma corrida por tempo", function () { 17 | const ride = TimeRide.create(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 18 | const lastLocation = new Location(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 19 | const newLocation = new Location(-27.496887588317275, -48.522234807851476, new Date("2021-03-01T12:00:00")); 20 | const segment = new TimeSegment(ride.rideId, lastLocation, newLocation); 21 | ride.updateLocation(new Location(-27.496887588317275, -48.522234807851476, new Date("2021-03-01T12:00:00"))); 22 | const fare = ride.calculateFare([segment]); 23 | expect(fare).toBe(120); 24 | }); 25 | -------------------------------------------------------------------------------- /test/gof/creational/factory_method/TimeSegment.test.ts: -------------------------------------------------------------------------------- 1 | import { TimeSegment } from "../../../../src/gof/creational/factory_method/Segment" 2 | import Location from "../../../../src/gof/creational/factory_method/Location"; 3 | 4 | test("Deve criar um segmento por tempo", function () { 5 | const fromLocation = new Location(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 6 | const toLocation = new Location(-27.496887588317275, -48.522234807851476, new Date("2021-03-01T12:00:00")); 7 | const timeSegment = new TimeSegment("", fromLocation, toLocation); 8 | expect(timeSegment.getDiffInMinutes()).toBe(120); 9 | }); 10 | -------------------------------------------------------------------------------- /test/gof/creational/factory_method/UpdateLocation.test.ts: -------------------------------------------------------------------------------- 1 | import CalculateFare from "../../../../src/gof/creational/factory_method/CalculateFare"; 2 | import Ride, { DistanceRide, TimeRide } from "../../../../src/gof/creational/factory_method/Ride"; 3 | import { RideRepositoryMemory } from "../../../../src/gof/creational/factory_method/RideRepository"; 4 | import { SegmentRepositoryMemory } from "../../../../src/gof/creational/factory_method/SegmentRepository"; 5 | import UpdateLocation from "../../../../src/gof/creational/factory_method/UpdateLocation"; 6 | 7 | test("Deve atualizar a localização de uma corrida por distância", async function () { 8 | const rideRepository = new RideRepositoryMemory(); 9 | const segmentRepository = new SegmentRepositoryMemory(); 10 | const ride = DistanceRide.create(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 11 | await rideRepository.save(ride); 12 | const updateLocation = new UpdateLocation(rideRepository, segmentRepository); 13 | const input = { 14 | rideId: ride.rideId, 15 | lat: -27.496887588317275, 16 | long: -48.522234807851476, 17 | date: new Date("2021-03-01T12:00:00") 18 | } 19 | await updateLocation.execute(input); 20 | const calculateFare = new CalculateFare(rideRepository, segmentRepository); 21 | const output = await calculateFare.execute(ride.rideId); 22 | expect(output.fare).toBe(40); 23 | }); 24 | 25 | test("Deve atualizar a localização de uma corrida por tempo", async function () { 26 | const rideRepository = new RideRepositoryMemory(); 27 | const segmentRepository = new SegmentRepositoryMemory(); 28 | const ride = TimeRide.create(-27.584905257808835, -48.545022195325124, new Date("2021-03-01T10:00:00")); 29 | await rideRepository.save(ride); 30 | const updateLocation = new UpdateLocation(rideRepository, segmentRepository); 31 | const input = { 32 | rideId: ride.rideId, 33 | lat: -27.496887588317275, 34 | long: -48.522234807851476, 35 | date: new Date("2021-03-01T12:00:00") 36 | } 37 | await updateLocation.execute(input); 38 | const calculateFare = new CalculateFare(rideRepository, segmentRepository); 39 | const output = await calculateFare.execute(ride.rideId); 40 | expect(output.fare).toBe(120); 41 | }); 42 | -------------------------------------------------------------------------------- /test/gof/creational/prototype/CopyForm.test.ts: -------------------------------------------------------------------------------- 1 | import CopyForm from "../../../../src/gof/creational/prototype/CopyForm"; 2 | import Form from "../../../../src/gof/creational/prototype/Form"; 3 | import { FormRepositoryMemory } from "../../../../src/gof/creational/prototype/FormRepository"; 4 | 5 | test("Deve copiar um formulário", async function () { 6 | const formRepository = new FormRepositoryMemory(); 7 | const form = new Form("1", "Marketing", "Leads v1"); 8 | form.addField("text", "name"); 9 | form.addField("text", "email"); 10 | formRepository.save(form); 11 | const copyForm = new CopyForm(formRepository); 12 | const input = { 13 | fromFormId: "1", 14 | newFormId: "2", 15 | newCategory: "Marketing", 16 | newDescription: "Leads v2" 17 | } 18 | await copyForm.execute(input); 19 | const newForm = await formRepository.getById("2"); 20 | expect(newForm.category).toBe("Marketing"); 21 | expect(newForm.description).toBe("Leads v2"); 22 | expect(newForm.fields).toHaveLength(2); 23 | expect(newForm.fields.at(0)?.title).toBe("name"); 24 | expect(newForm.fields.at(1)?.title).toBe("email"); 25 | }); 26 | -------------------------------------------------------------------------------- /test/gof/creational/singleton/Signup.test.ts: -------------------------------------------------------------------------------- 1 | import Login from "../../../../src/gof/creational/singleton/Login"; 2 | import Signup from "../../../../src/gof/creational/singleton/Signup"; 3 | 4 | test("Deve criar uma conta de usuário", async function () { 5 | const signup = new Signup(); 6 | const login = new Login(); 7 | const inputSignup = { 8 | name: "John Doe", 9 | email: "john.doe@gmail.com", 10 | password: "123456" 11 | } 12 | await signup.execute(inputSignup); 13 | const inputLogin = { 14 | email: "john.doe@gmail.com", 15 | password: "123456" 16 | } 17 | const outputLogin = await login.execute(inputLogin); 18 | expect(outputLogin.success).toBe(true); 19 | }); 20 | -------------------------------------------------------------------------------- /test/gof/structural/adapter/catalog/api.test.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | test("Deve consultar um produto do catálogo", async function () { 4 | const productId = 1; 5 | const response = await axios.get(`http://localhost:3001/products/${productId}`); 6 | const output = response.data; 7 | expect(output.productId).toBe(1); 8 | expect(output.description).toBe("A"); 9 | expect(output.price).toBe(100); 10 | }); 11 | -------------------------------------------------------------------------------- /test/gof/structural/adapter/checkout/CalculateCheckout.test.ts: -------------------------------------------------------------------------------- 1 | import CalculateCheckout from "../../../../../src/gof/structural/adapter/checkout/CalculateCheckout"; 2 | import { CatalogGatewayHttp } from "../../../../../src/gof/structural/adapter/checkout/CatalogGateway"; 3 | import { AxiosAdapter, FetchAdapter } from "../../../../../src/gof/structural/adapter/checkout/HttpClient"; 4 | 5 | test("Deve calcular o checkout", async function () { 6 | const input = { 7 | items: [ 8 | { productId: 1, quantity: 1 }, 9 | { productId: 2, quantity: 2 }, 10 | { productId: 3, quantity: 3 }, 11 | ] 12 | }; 13 | // const httpClient = new AxiosAdapter(); 14 | const httpClient = new FetchAdapter(); 15 | const catalogGateway = new CatalogGatewayHttp(httpClient); 16 | const calculateCheckout = new CalculateCheckout(catalogGateway); 17 | const output = await calculateCheckout.execute(input); 18 | expect(output.total).toBe(1400); 19 | }); 20 | -------------------------------------------------------------------------------- /test/gof/structural/bridge/Account.test.ts: -------------------------------------------------------------------------------- 1 | import Driver from "../../../../src/gof/structural/bridge/Driver"; 2 | import Passenger from "../../../../src/gof/structural/bridge/Passenger"; 3 | 4 | 5 | test("Deve criar uma conta de usuário do tipo passageiro", function () { 6 | const account = new Passenger("John Doe", "john.doe@gmail.com", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456"); 7 | expect(account.name).toBe("John Doe"); 8 | expect(account.email).toBe("john.doe@gmail.com"); 9 | }); 10 | 11 | test("Não deve criar uma conta de usuário do tipo passageiro com o nome inválido", function () { 12 | expect(() => new Passenger("John", "john.doe@gmail.com", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456")).toThrow(new Error("Invalid name")); 13 | }); 14 | 15 | test("Não deve criar uma conta de usuário do tipo passageiro com o email inválido", function () { 16 | expect(() => new Passenger("John Doe", "john.doe@gmail", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456")).toThrow(new Error("Invalid email")); 17 | }); 18 | 19 | test("Não deve criar uma conta de usuário do tipo passageiro com o documento inválido", function () { 20 | expect(() => new Passenger("John Doe", "john.doe@gmail.com", "1111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456")).toThrow(new Error("Invalid document")); 21 | }); 22 | 23 | test("Não deve criar uma conta de usuário do tipo passageiro com o cvv inválido", function () { 24 | expect(() => new Passenger("John Doe", "john.doe@gmail.com", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "12", "123456")).toThrow(new Error("Invalid cvv")); 25 | }); 26 | 27 | test("Deve criar uma conta de usuário do tipo motorista", function () { 28 | const account = new Driver("John Doe", "john.doe@gmail.com", "11111111111", "AAA9999", "123456"); 29 | expect(account.name).toBe("John Doe"); 30 | expect(account.email).toBe("john.doe@gmail.com"); 31 | }); 32 | 33 | test("Não deve criar uma conta de usuário do tipo motorista com a placa do carro inválida", function () { 34 | expect(() => new Driver("John Doe", "john.doe@gmail.com", "11111111111", "AAA999", "123546")).toThrow(new Error("Invalid car plate")); 35 | }); 36 | 37 | test("Deve validar a senha armazenada em texto plano de uma conta de usuário do tipo passageiro", function () { 38 | const account = new Passenger("John Doe", "john.doe@gmail.com", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456", "plaintext"); 39 | console.log(account.password); 40 | expect(account.passwordMatches("123456")).toBe(true); 41 | }); 42 | 43 | test("Deve validar a senha armazenada em SHA1 de uma conta de usuário do tipo passageiro", function () { 44 | const account = new Passenger("John Doe", "john.doe@gmail.com", "11111111111", "JOHN DOE", "1111 1111 1111 1111", "08/28", "123", "123456", "sha1"); 45 | console.log(account.password); 46 | expect(account.passwordMatches("123456")).toBe(true); 47 | }); -------------------------------------------------------------------------------- /test/gof/structural/decorator/BookRoom.test.ts: -------------------------------------------------------------------------------- 1 | import BookRoom from "../../../../src/gof/structural/decorator/BookRoom"; 2 | import { BookingRepositoryDatabase } from "../../../../src/gof/structural/decorator/BookingRepository"; 3 | import CancelBooking from "../../../../src/gof/structural/decorator/CancelBooking"; 4 | import GetBookingByCode from "../../../../src/gof/structural/decorator/GetBookingByCode"; 5 | import { RoomRepositoryDatabase } from "../../../../src/gof/structural/decorator/RoomRepository"; 6 | 7 | test("Deve reservar um quarto", async function () { 8 | const roomRepository = new RoomRepositoryDatabase(); 9 | const bookingRepository = new BookingRepositoryDatabase(); 10 | const bookRoom = new BookRoom(roomRepository, bookingRepository); 11 | const input = { 12 | email: "john.doe@gmail.com", 13 | checkinDate: new Date("2021-03-01T10:00:00"), 14 | checkoutDate: new Date("2021-03-05T10:00:00"), 15 | category: "suite" 16 | } 17 | const outputBookRoom = await bookRoom.execute(input); 18 | const getBookingByCode = new GetBookingByCode(roomRepository, bookingRepository); 19 | const outputGetBookingByCode = await getBookingByCode.execute({ code: outputBookRoom.code }); 20 | expect(outputGetBookingByCode.duration).toBe(4); 21 | expect(outputGetBookingByCode.price).toBe(2000); 22 | const cancelBooking = new CancelBooking(bookingRepository); 23 | await cancelBooking.execute({ code: outputBookRoom.code }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/gof/structural/decorator/ImportBooking.test.ts: -------------------------------------------------------------------------------- 1 | import BookRoom from "../../../../src/gof/structural/decorator/BookRoom"; 2 | import { BookingRepositoryDatabase } from "../../../../src/gof/structural/decorator/BookingRepository"; 3 | import CancelBooking from "../../../../src/gof/structural/decorator/CancelBooking"; 4 | import GetBookingByCode from "../../../../src/gof/structural/decorator/GetBookingByCode"; 5 | import ImportBooking from "../../../../src/gof/structural/decorator/ImportBooking"; 6 | import LogDecorator from "../../../../src/gof/structural/decorator/LogDecorator"; 7 | import { RoomRepositoryDatabase } from "../../../../src/gof/structural/decorator/RoomRepository"; 8 | import SecurityDecorator from "../../../../src/gof/structural/decorator/SecurityDecorator"; 9 | 10 | test("Deve importar uma lista de reservas", async function () { 11 | const roomRepository = new RoomRepositoryDatabase(); 12 | const bookingRepository = new BookingRepositoryDatabase(); 13 | const input = { 14 | file: `email;checkin_date;checkout_date;category; 15 | john.doe1@gmail.com;2021-03-01T10:00:00;2021-03-03T10:00:00;suite; 16 | john.doe2@gmail.com;2021-03-06T10:00:00;2021-03-08T10:00:00;suite; 17 | john.doe3@gmail.com;2021-03-20T10:00:00;2021-03-22T10:00:00;suite;` 18 | }; 19 | const importBooking = new SecurityDecorator(new LogDecorator(new ImportBooking(new LogDecorator(new BookRoom(roomRepository, bookingRepository))))); 20 | const outputImportBooking = await importBooking.execute(input); 21 | for (const code of outputImportBooking.code) { 22 | const getBookingByCode = new GetBookingByCode(roomRepository, bookingRepository); 23 | const outputGetBookingByCode = await getBookingByCode.execute({ code }); 24 | expect(outputGetBookingByCode.duration).toBe(2); 25 | expect(outputGetBookingByCode.price).toBe(1000); 26 | const cancelBooking = new CancelBooking(bookingRepository); 27 | await cancelBooking.execute({ code }); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /test/gof/structural/decorator/RoomRepository.test.ts: -------------------------------------------------------------------------------- 1 | import { RoomRepositoryDatabase } from "../../../../src/gof/structural/decorator/RoomRepository"; 2 | 3 | test("Deve obter um quarto", async function () { 4 | const roomRepository = new RoomRepositoryDatabase(); 5 | const room = await roomRepository.getById(1); 6 | expect(room.category).toBe("suite"); 7 | expect(room.price).toBe(500); 8 | }); 9 | 10 | test("Deve obter um quarto quarto disponível para reserva em um período", async function () { 11 | const roomRepository = new RoomRepositoryDatabase(); 12 | const [room] = await roomRepository.getAvailableRoomsByPeriodAndCategory(new Date("2021-03-01T10:00:00"), new Date("2021-03-05T10:00:00"), "suite"); 13 | expect(room.category).toBe("suite"); 14 | expect(room.price).toBe(500); 15 | }); 16 | 17 | test("Não deve obter um quarto quarto disponível para reserva em um período", async function () { 18 | const roomRepository = new RoomRepositoryDatabase(); 19 | const rooms = await roomRepository.getAvailableRoomsByPeriodAndCategory(new Date("2021-03-11T15:00:00"), new Date("2021-03-12T10:00:00"), "suite"); 20 | expect(rooms).toHaveLength(0); 21 | }); 22 | -------------------------------------------------------------------------------- /test/poeaa/orm/ORM.test.ts: -------------------------------------------------------------------------------- 1 | import AccountModel from "../../../src/poeaa/orm/AccountModel"; 2 | import { PgPromiseAdapter } from "../../../src/poeaa/orm/DatabaseConnection"; 3 | import ORM from "../../../src/poeaa/orm/ORM"; 4 | import crypto from "crypto"; 5 | 6 | test("Deve testar o ORM", async function () { 7 | const connection = new PgPromiseAdapter(); 8 | const orm = new ORM(connection); 9 | const accountId = crypto.randomUUID(); 10 | const account = new AccountModel(accountId, "a", "b", "c", "d"); 11 | await orm.save(account); 12 | const savedAccount = await orm.get("account_id", accountId, AccountModel); 13 | console.log(savedAccount); 14 | await connection.close(); 15 | }); 16 | -------------------------------------------------------------------------------- /test/poeaa/repository/User.test.ts: -------------------------------------------------------------------------------- 1 | import User from "../../../src/poeaa/repository/User"; 2 | 3 | test("Deve criar um novo usuário", async function () { 4 | const user = new User("John Doe", "john.doe@gmail.com", "abc123456"); 5 | expect(user.getName()).toBe("John Doe"); 6 | expect(user.getEmail()).toBe("john.doe@gmail.com"); 7 | expect(user.getPassword()).toBe("abc123456"); 8 | expect(user.getStatus()).toBe("active"); 9 | }); 10 | 11 | test("Deve modificar a senha de usuário", async function () { 12 | const user = new User("John Doe", "john.doe@gmail.com", "abc123456"); 13 | user.updatePassword("asd456789"); 14 | expect(user.getPassword()).toBe("asd456789"); 15 | }); 16 | 17 | test("Não deve modificar a senha do usuário se ela não tiver no mínimo 8 caracteres", async function () { 18 | const user = new User("John Doe", "john.doe@gmail.com", "abc123456"); 19 | expect(() => user.updatePassword("asd456")).toThrow(new Error("Minimum length is 8")); 20 | }); 21 | 22 | test("Não deve modificar o email do usuário se ele for inválido", async function () { 23 | const user = new User("John Doe", "john.doe@gmail.com", "abc123456"); 24 | expect(() => user.updateEmail("john.doe")).toThrow(new Error("Invalid email")); 25 | }); 26 | 27 | test("Deve bloquear o usuário", async function () { 28 | const user = new User("John Doe", "john.doe@gmail.com", "abc123456"); 29 | user.block(); 30 | expect(user.getStatus()).toBe("blocked"); 31 | }); 32 | -------------------------------------------------------------------------------- /test/poeaa/repository/UserRepository.test.ts: -------------------------------------------------------------------------------- 1 | import User from "../../../src/poeaa/repository/User"; 2 | import { UserRepositoryDatabase } from "../../../src/poeaa/repository/UserRepository"; 3 | 4 | test("Deve salvar um novo usuário", async function () { 5 | const email = `john.doe${Math.random()}@gmail.com`; 6 | const user = new User("John Doe", email, "abc123456"); 7 | const userRepository = new UserRepositoryDatabase(); 8 | await userRepository.save(user); 9 | const savedUser = await userRepository.getByEmail(email); 10 | expect(savedUser.getName()).toBe("John Doe"); 11 | expect(savedUser.getEmail()).toBe(email); 12 | expect(savedUser.getPassword()).toBe("abc123456"); 13 | expect(savedUser.getStatus()).toBe("active"); 14 | await userRepository.delete(email); 15 | }); 16 | 17 | test("Deve atualizar um usuário", async function () { 18 | const email = `john.doe${Math.random()}@gmail.com`; 19 | const user = new User("John Doe", email, "abc123456"); 20 | const userRepository = new UserRepositoryDatabase(); 21 | await userRepository.save(user); 22 | const savedUser = await userRepository.getByEmail(email); 23 | savedUser.updatePassword("asd456789"); 24 | await userRepository.update(savedUser); 25 | const updatedUser = await userRepository.getByEmail(email); 26 | expect(updatedUser.getPassword()).toBe("asd456789"); 27 | await userRepository.delete(email); 28 | }); 29 | 30 | test("Deve listar três usuários", async function () { 31 | const userRepository = new UserRepositoryDatabase(); 32 | await userRepository.save(new User("John Doe", `john.doe1@gmail.com`, "abc123456")); 33 | await userRepository.save(new User("John Doe", `john.doe2@gmail.com`, "abc123456")); 34 | await userRepository.save(new User("John Doe", `john.doe3@gmail.com`, "abc123456")); 35 | const users = await userRepository.list(); 36 | expect(users).toHaveLength(3); 37 | await userRepository.delete(`john.doe1@gmail.com`); 38 | await userRepository.delete(`john.doe2@gmail.com`); 39 | await userRepository.delete(`john.doe3@gmail.com`); 40 | }); 41 | -------------------------------------------------------------------------------- /test/poeaa/transaction_script/main.test.ts: -------------------------------------------------------------------------------- 1 | test("Deve solicitar um financiamento imobiliário", async function () { 2 | 3 | }); 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 39 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 40 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 41 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 42 | // "resolveJsonModule": true, /* Enable importing .json files. */ 43 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 57 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 58 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 59 | // "removeComments": true, /* Disable emitting comments. */ 60 | // "noEmit": true, /* Disable emitting files from a compilation. */ 61 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 62 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 63 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 64 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 65 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 79 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 80 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 81 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 82 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 83 | 84 | /* Type Checking */ 85 | "strict": true, /* Enable all strict type-checking options. */ 86 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 87 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 88 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 89 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 90 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 91 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 92 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 93 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 94 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 95 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 96 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 97 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 98 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 99 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 100 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 101 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 102 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 103 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 104 | 105 | /* Completeness */ 106 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 107 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 108 | } 109 | } 110 | --------------------------------------------------------------------------------