Skip to content

Commit

Permalink
centralized database config and add validation and tests for create a…
Browse files Browse the repository at this point in the history
…nd transaction update
  • Loading branch information
sebastiansj70 committed Oct 8, 2024
1 parent 3db9ffe commit 35948c5
Show file tree
Hide file tree
Showing 13 changed files with 6,175 additions and 1,014 deletions.
Original file line number Diff line number Diff line change
@@ -1,47 +1,66 @@
import { Request, Response } from 'express';
import { UpdateTransactionUseCase } from '../../application/updateTransaction.usecase';
import { TransactionRepositoryImpl } from '../../domain/repositories/transaction.repository';
import { TransactionRepository } from '../../domain/repositories/transaction.repository';
import { CreateTransactionUseCase } from '../../application/createTransaction.usecase';
import { CreateTransactionDTO } from '../../domain/dtos/createTransaction.dto';
import { validateCreateTransactionDto, validateUpdateTransactionBody } from '../validation/transaction.validator';

const transactionRepository = new TransactionRepositoryImpl();
const updateTransactionUseCase = new UpdateTransactionUseCase(transactionRepository);
const createTransactionUseCase = new CreateTransactionUseCase(transactionRepository);

export class TransactionController {
private transactionRepository: TransactionRepository;

export const createTransaction = async (req: Request, res: Response) => {
try {
const { accountExternalIdDebit, accountExternalIdCredit, transferTypeId, value } = req.body;
constructor(transactionRepository: TransactionRepository) {
this.transactionRepository = transactionRepository
}

const createTransactionDto: CreateTransactionDTO = {
accountExternalIdDebit,
accountExternalIdCredit,
transferTypeId,
value
};

public createTransaction = async (req: Request, res: Response) => {
try {

const savedTransaction = await createTransactionUseCase.execute(createTransactionDto);
if (!validateCreateTransactionDto(req.body)) {
return res.status(400).json({ message: 'incorrect body' });
}

return res.status(200).json(savedTransaction);
} catch (error) {
console.error('Error creating transaction:', error);
return res.status(500).json({ message: 'Error creating transaction', error });
}
};
const { accountExternalIdDebit, accountExternalIdCredit, transferTypeId, value } = req.body;

const createTransactionDto: CreateTransactionDTO = {
accountExternalIdDebit,
accountExternalIdCredit,
transferTypeId,
value
};

export const updateTransaction = async (req: Request, res: Response) => {
const createTransactionUseCase = new CreateTransactionUseCase(this.transactionRepository)
const savedTransaction = await createTransactionUseCase.execute(createTransactionDto);

try {
const { id } = req.params;
const updatedData = req.body;
return res.status(200).json(savedTransaction);
} catch (error) {
console.error('Error creating transaction:', error);
return res.status(500).json({ message: 'Error creating transaction', error });
}
};

const updateTransaction = await updateTransactionUseCase.execute(id, updatedData)
public updateTransaction = async (req: Request, res: Response): Promise<Response | void> => {
try {

return res.status(200).json(updateTransaction)
} catch (error) {
console.error('Error updating transaction:', error);
return res.status(500).json({ message: 'Error updating transaction', error })
}
}
if (!validateUpdateTransactionBody(req.body)) {
return res.status(400).json({ message: 'incorrect body' });
}

const { id } = req.params;
const updatedData = req.body;

const updateTransactionUseCase = new UpdateTransactionUseCase(this.transactionRepository);
const updatedTransaction = await updateTransactionUseCase.execute(id, updatedData);

return res.status(200).json(updatedTransaction);
} catch (error: any) {
if (error?.message === 'Transaction not found') {
return res.status(404).json({ message: 'Transaction not found' });
}
console.error('Error updating transaction:', error);
return res.status(500).json({ message: 'Error updating transaction', error });
}
};

}
14 changes: 7 additions & 7 deletions apps/transactions/src/adapters/routes/transaction.routes.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { Router, Request, Response } from 'express';
import { createTransaction, updateTransaction } from '../controllers/transaction.controller';
import { TransactionController } from '../controllers/transaction.controller';
import { TransactionRepositoryImpl } from '../../domain/repositories/transaction.repository';

const router = Router();
const transactionRepository = new TransactionRepositoryImpl();
const transactionController = new TransactionController(transactionRepository)

router.get('/', (req: Request, res: Response) => {
res.json({ message: "Hello, this is the transactions API!" });
});

router.post('/create', async (req: Request, res: Response) => {
await createTransaction(req, res)
await transactionController.createTransaction(req, res)
});

router.put('/:id', async (req: Request, res: Response) => {
await updateTransaction(req, res)
})
await transactionController.updateTransaction(req, res);
});

export default router;
37 changes: 37 additions & 0 deletions apps/transactions/src/adapters/validation/transaction.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { CreateTransactionDTO } from '../../domain/dtos/createTransaction.dto';
import { UpdatedData } from '../../domain/dtos/updatedTransaction.dto';

export const validateCreateTransactionDto = (data: any): data is CreateTransactionDTO => {
const { accountExternalIdDebit, accountExternalIdCredit, transferTypeId, value } = data;

if (
typeof accountExternalIdDebit !== 'string' ||
typeof accountExternalIdCredit !== 'string' ||
typeof transferTypeId !== 'number' ||
![1, 2, 3].includes(transferTypeId) ||
typeof value !== 'number' ||
value <= 0
) {
return false;
}

return true;
};

export const validateUpdateTransactionBody = (data: any): data is UpdatedData => {
const { transactionType, transactionStatus, value, createdAt } = data;

if (
typeof transactionType !== 'object' ||
typeof transactionType.name !== 'number' ||
typeof transactionStatus !== 'object' ||
typeof transactionStatus.name !== 'string' ||
typeof value !== 'number' ||
value <= 0 ||
!(createdAt instanceof Date)
) {
return false;
}

return true;
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { UpdatedData } from "../domain/dtos/updatedTransaction.dto";
import { Transaction } from "../domain/entities/transaction.entity";
import { TransactionRepository } from '../domain/repositories/transaction.repository';


export class UpdateTransactionUseCase {
constructor(private transactionRepository: TransactionRepository) { }

async execute(id: string, updatedData: Partial<Transaction>): Promise<Transaction> {
async execute(id: string, updatedData: UpdatedData): Promise<Transaction> {
const transaction = await this.transactionRepository.findById(id)
if (!transaction) {
throw new Error('Transaction not found')
}
return await this.transactionRepository.update(id, updatedData)
transaction.status = updatedData.transactionStatus.name
transaction.transferTypeId = updatedData.transactionType.name
await this.transactionRepository.update(id, { status: transaction.status, transferTypeId: transaction.transferTypeId })
return transaction
}
}
11 changes: 11 additions & 0 deletions apps/transactions/src/domain/dtos/updatedTransaction.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface UpdatedData {
transactionExternalId: string;
transactionType: {
name: number;
};
transactionStatus: {
name: string;
};
value: number;
createdAt: Date;
}
3 changes: 2 additions & 1 deletion apps/transactions/src/domain/entities/transaction.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
const isSQLite = process.env.NODE_ENV === 'test'

@Entity('transactions')
export class Transaction {
Expand All @@ -20,7 +21,7 @@ export class Transaction {
@Column({ default: 'pending' })
status: string;

@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
@Column({ type: isSQLite ? 'datetime' : 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;

constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { Repository } from 'typeorm';
import { Repository, UpdateResult } from 'typeorm';
import { Transaction } from '../entities/transaction.entity';
import AppDataSource from '../../infrastructure/database/transaction.ormconfig';

Expand All @@ -8,7 +8,7 @@ export interface TransactionRepository {
save(transaction: Transaction): Promise<Transaction>;
findById(id: string): Promise<Transaction | null>;
findAll(): Promise<Transaction[]>;
update(id: string, updatedTransaction: Partial<Transaction>): Promise<Transaction>
update(id: string, updatedTransaction: Partial<Transaction>): Promise<UpdateResult>
}

export class TransactionRepositoryImpl implements TransactionRepository {
Expand All @@ -30,12 +30,8 @@ export class TransactionRepositoryImpl implements TransactionRepository {
return await this.repository.find();
}

async update(id: string, updatedTransaction: Partial<Transaction>): Promise<Transaction> {
await this.repository.update(id, updatedTransaction);
const transaction = await this.findById(id)
if (!transaction) {
throw new Error('Transaction not found')
}
return transaction
async update(id: string, updatedTransaction: Partial<Transaction>): Promise<UpdateResult> {
return await this.repository.update(id, updatedTransaction);

}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import { DataSource } from 'typeorm';
import { Transaction } from '../../domain/entities/transaction.entity';
import dotenv from 'dotenv';

const AppDataSource = new DataSource({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'postgres',
password: '123456',
database: 'postgres',
synchronize: true,
logging: true,
entities: [Transaction],
});
let AppDataSource: DataSource
if (process.env.NODE_ENV === 'test') {
dotenv.config({ path: '.env.test' });
AppDataSource = new DataSource({
type: 'sqlite',
database: './db.sqlite',
synchronize: true,
logging: ['query', 'error'],
entities: [Transaction],
});
} else {
dotenv.config();
AppDataSource = new DataSource({
type: process.env.DB_TYPE as 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432', 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
synchronize: Boolean(process.env.DB_SYNCHRONIZE),
logging: Boolean(process.env.DB_LOGGING),
entities: [Transaction],
});
}

export default AppDataSource;
Loading

0 comments on commit 35948c5

Please sign in to comment.