Skip to content

Commit

Permalink
upload de arquivo csv e impotação - refatorando código
Browse files Browse the repository at this point in the history
  • Loading branch information
tgmarinho committed Apr 17, 2020
1 parent 28e933d commit 98152d0
Show file tree
Hide file tree
Showing 10 changed files with 325 additions and 78 deletions.
19 changes: 19 additions & 0 deletions src/config/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import crypto from 'crypto';
import multer from 'multer';
import path from 'path';

const tmpFolder = path.resolve(__dirname, '..', '..', 'tmp');

export default {
directory: tmpFolder,

storage: multer.diskStorage({
destination: tmpFolder,
filename(request, file, callback) {
const fileHash = crypto.randomBytes(10).toString('HEX');
const fileName = `${fileHash}-${file.originalname}`;

return callback(null, fileName);
},
}),
};
6 changes: 4 additions & 2 deletions src/models/Category.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Exclusion,
} from 'typeorm';

@Entity('categories')
Expand All @@ -14,10 +15,11 @@ class Category {
@Column()
title: string;

@CreateDateColumn()
@CreateDateColumn({ select: false })
created_at: Date;

@UpdateDateColumn()
@Column({ select: false })
@UpdateDateColumn({ select: false })
updated_at: Date;
}

Expand Down
4 changes: 2 additions & 2 deletions src/models/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class Transaction {
@JoinColumn({ name: 'category_id' })
category: Category;

@CreateDateColumn()
@CreateDateColumn({ select: false })
created_at: Date;

@UpdateDateColumn()
@UpdateDateColumn({ select: false })
updated_at: Date;
}

Expand Down
39 changes: 38 additions & 1 deletion src/repositories/TransactionsRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,47 @@ interface Balance {
total: number;
}

enum Type {
INCOME = 'income',
OUTCOME = 'outcome',
}

interface TypeDTO {
income: number;
outcome: number;
}

@EntityRepository(Transaction)
class TransactionsRepository extends Repository<Transaction> {
private calculateBalance(transactions: Array<Transaction>): TypeDTO {
return transactions.reduce(
(accumulator, transaction) => {
switch (transaction.type) {
case Type.INCOME:
accumulator.income += Number(transaction.value);
break;
case Type.OUTCOME:
accumulator.outcome += Number(transaction.value);
break;
default:
break;
}

return accumulator;
},
{ income: 0, outcome: 0 },
);
}

public async getBalance(): Promise<Balance> {
// TODO
const transactions = await this.find();

const { income, outcome } = this.calculateBalance(transactions);
const total = income - outcome;

const balance: Balance = { income, outcome, total };

return balance;
}
}

Expand Down
57 changes: 47 additions & 10 deletions src/routes/transactions.routes.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,63 @@
import { Router } from 'express';
import { getCustomRepository } from 'typeorm';
import multer from 'multer';
import uploadConfig from '../config/upload';

// import TransactionsRepository from '../repositories/TransactionsRepository';
// import CreateTransactionService from '../services/CreateTransactionService';
// import DeleteTransactionService from '../services/DeleteTransactionService';
// import ImportTransactionsService from '../services/ImportTransactionsService';
import TransactionsRepository from '../repositories/TransactionsRepository';
import CreateTransactionService from '../services/CreateTransactionService';
import DeleteTransactionService from '../services/DeleteTransactionService';
import ImportTransactionsService from '../services/ImportTransactionsService';

const transactionsRouter = Router();
const upload = multer(uploadConfig);

transactionsRouter.get('/', async (request, response) => {
// TODO
const transactionsRepositories = getCustomRepository(TransactionsRepository);

const transactions = await transactionsRepositories.find({
select: ['id', 'title', 'value', 'type', 'category'],
relations: ['category'],
});

const balance = await transactionsRepositories.getBalance();
return response.json({ transactions, balance });
});

transactionsRouter.post('/', async (request, response) => {
// TODO
const { title, value, type, category } = request.body;

const createTransationService = new CreateTransactionService();
const transaction = await createTransationService.execute({
title,
value,
type,
category,
});

return response.json(transaction);
});

transactionsRouter.delete('/:id', async (request, response) => {
// TODO
});
const { id } = request.params;

transactionsRouter.post('/import', async (request, response) => {
// TODO
const deleteTransactionService = new DeleteTransactionService();

await deleteTransactionService.execute({ id });

return response.status(200).send();
});

transactionsRouter.post(
'/import',
upload.single('file'),
async (request, response) => {
const importTransactionsService = new ImportTransactionsService();
const transactions = await importTransactionsService.execute({
csvFile: request.file.filename,
});

return response.json(transactions);
},
);

export default transactionsRouter;
59 changes: 55 additions & 4 deletions src/services/CreateTransactionService.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,61 @@
// import AppError from '../errors/AppError';

import { getCustomRepository, getRepository } from 'typeorm';
import AppError from '../errors/AppError';
import TransactionsRepository from '../repositories/TransactionsRepository';
import Transaction from '../models/Transaction';
import Category from '../models/Category';

interface Request {
title: string;
value: number;
type: 'income' | 'outcome';
category: {
title: string;
};
}

class CreateTransactionService {
public async execute(): Promise<Transaction> {
// TODO
public async execute({
title,
value,
type,
category,
}: Request): Promise<Transaction> {
try {
const transactionsRepository = getCustomRepository(
TransactionsRepository,
);

const categoryRepository = getRepository(Category);

const categoryExistsWithTitle = await categoryRepository.findOne({
where: { title: category.title },
});

let categoryCreated = null;

if (!categoryExistsWithTitle) {
categoryCreated = await categoryRepository.create({
title: category.title,
});

await categoryRepository.save(categoryCreated);
}

const transaction = await transactionsRepository.create({
title,
value,
type,
category_id:
(categoryExistsWithTitle && categoryExistsWithTitle.id) ||
categoryCreated?.id,
});

await transactionsRepository.save(transaction);

return transaction;
} catch (error) {
throw new AppError('Ops, error! the admin was notified', 500);
}
}
}

Expand Down
18 changes: 15 additions & 3 deletions src/services/DeleteTransactionService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
// import AppError from '../errors/AppError';
import { getCustomRepository } from 'typeorm';
import AppError from '../errors/AppError';

import TransactionRepository from '../repositories/TransactionsRepository';

interface Request {
id: string;
}

class DeleteTransactionService {
public async execute(): Promise<void> {
// TODO
public async execute({ id }: Request): Promise<void> {
const transactionRepository = getCustomRepository(TransactionRepository);
const transaction = await transactionRepository.findOne(id);
if (!transaction) {
throw new AppError('Transactions not exist');
}
await transactionRepository.delete(id);
}
}

Expand Down
81 changes: 79 additions & 2 deletions src/services/ImportTransactionsService.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,85 @@
import parse from 'csv-parse';
import fs from 'fs';
import path from 'path';
import Transaction from '../models/Transaction';
import CreateTransactionService from './CreateTransactionService';
import upload from '../config/upload';
import AppError from '../errors/AppError';

interface Request {
csvFile: string;
}

interface TransactionDTO {
title: string;
type: 'income' | 'outcome';
value: number;
category: { title: string };
}

class ImportTransactionsService {
async execute(): Promise<Transaction[]> {
// TODO
private getTransactionsFromCSV(
csvFile: string,
): Promise<Array<TransactionDTO>> {
const filePath = path.resolve(upload.directory, csvFile);

const csvReadStream = fs.createReadStream(filePath);

const parsers = parse({ delimiter: ', ', from_line: 2 });

const parsed = csvReadStream.pipe(parsers);

fs.unlink(filePath, error => {
if (error) throw error;
// eslint-disable-next-line no-console
console.log(`${csvFile} was deleted.`);
});

return new Promise((resolve, reject) => {
const transactions: Array<TransactionDTO> = [];
parsed
.on('data', line => {
const [title, type, value, category] = line;

transactions.push({
title,
type,
value,
category: { title: category },
});
})
.on('error', () => {
reject(new AppError('Error to read from csv file', 500));
})
.on('end', () => {
resolve(transactions);
});
});
}

async execute({ csvFile }: Request): Promise<Transaction[]> {
try {
const createTransaction = new CreateTransactionService();

let transactionsParsed: TransactionDTO[] = [];

transactionsParsed = await this.getTransactionsFromCSV(csvFile);

const transactionsPersisted: Transaction[] = [];

// eslint-disable-next-line no-restricted-syntax
for (const transaction of transactionsParsed) {
// eslint-disable-next-line no-await-in-loop
const transactionSaved = await createTransaction.execute(transaction);
transactionsPersisted.push(transactionSaved);
}

return transactionsPersisted;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
throw new AppError('Error to read and save transactions', 500);
}
}
}

Expand Down
Loading

0 comments on commit 98152d0

Please sign in to comment.