Currently, I am in the process of developing a finance tracker application that involves importing data from a CSV file. The import functionality checks if an entry already exists in the database, adds a specific category to it if not found, and then saves it to the database.
After importing the CSV file, I intend to provide a response to the Post Request that includes the number of imported entries and details of entries for which no suitable category was found. However, due to the asynchronous nature of Nestjs, I encounter issues where my response is output before all functions are completed. This leads to the initial import always showing “imports: 0” and “unsortedTransactions: []”
I am seeking guidance on how to ensure that the controller waits for all functions to complete before returning a response.
TransactionController:@Controller('transactions')
export class TransactionController {
constructor(
private transactionService: TransactionService,
private labelService: LabelService,
) {}
@Post()
@UseInterceptors(
FileInterceptor('file', {
storage: diskStorage({
destination: './uploads/csv',
filename: randomFilename,
}),
}),
)
public createTransactions(
@Response() response,
@UploadedFile() file,
@Request() request,
) {
let imports = 0;
fs.createReadStream(path.resolve(file.path), {
encoding: 'utf-8',
})
.pipe(csv.parse({ headers: false, delimiter: ';', skipRows: 1 }))
.on('data', (data) => {
const timestamp = new Date()
.toISOString()
.slice(0, 19)
.replace('T', ' ');
const amount = parseFloat(data[14].replace(',', '.'));
const bankaccountId = request.body.bankaccount_id;
const bookingDate = this.createDate(data[1]);
const name = data[11].replace(/\s\s+/g, ' ');
const usage = data[4].replace(/\s\s+/g, ' ');
this.transactionService
.checkDuplicate(amount, name, usage, bookingDate)
.then((entry) => {
if (entry.length === 0) {
this.sortLabel(data).then((label_id) => {
const newTransaction = {
amount,
name,
usage,
label_id,
bankaccount_id: bankaccountId,
booking_date: bookingDate,
created_at: timestamp,
updated_at: timestamp,
};
this.transactionService.create(newTransaction);
imports++;
});
}
});
});
const unsortedTransactions = this.transactionService.getUnsorted();
return response.send({ unsortedTransactions, imports });
}
private async sortLabel(transaction: Transaction): Promise<any> {
let label_id = 1;
const labels = await this.labelService.getAll();
const name = transaction[11].replace(/\s\s+/g, ' ').toLowerCase();
const usage = transaction[4].replace(/\s\s+/g, ' ').toLowerCase();
labels.forEach((label) => {
if (label.keywords != null) {
const keywords = label.keywords.split(',');
keywords.forEach((keyword) => {
if (
name.includes(keyword.toLowerCase()) ||
usage.includes(keyword.toLowerCase())
) {
label_id = label.id;
}
});
}
});
return await label_id;
}
private createDate(date: string): string {
const dateArray = date.split('.');
return `20${dateArray[2]}-${dateArray[1]}-${dateArray[0]}`;
}
}
Transaction Service:
export class TransactionService {
constructor(
@InjectRepository(Transaction)
private readonly transactionRepository: Repository<Transaction>,
) {}
public create(transaction): Promise<Transaction> {
return this.transactionRepository.save(transaction);
}
public getAll(): Promise<Transaction[]> {
return this.transactionRepository.find({
relations: ['label_id', 'bankaccount_id'],
});
}
public getUnsorted(): Promise<Transaction[]> {
return this.transactionRepository.find({
where: {
label_id: 1,
},
});
}
public checkDuplicate(
amount: number,
name: string,
usage: string,
booking_date: string,
): Promise<Transaction[]> {
return this.transactionRepository.find({
where: {
amount,
name,
usage,
booking_date,
},
});
}
}