diff --git a/.gitignore b/.gitignore index ff3d300..cd10fad 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ _fresh/ node_modules/ sqlite* +.serena diff --git a/db/models/expense.ts b/db/models/expense.ts index 43b3acc..598711c 100644 --- a/db/models/expense.ts +++ b/db/models/expense.ts @@ -189,8 +189,8 @@ export default class ExpenseService { }); } - // TODO make this more atomic. if one fails, all should fail - expensesToUpdate.forEach(async (expense) => { + // Update all expenses atomically - if one fails, all fail + const updatePromises = expensesToUpdate.map(async (expense) => { const expenseKey = [Keys.EXPENSES, userId, expense.id]; const correlationKey = [ Keys.EXPENSES_BY_CORRELATION, @@ -206,15 +206,17 @@ export default class ExpenseService { expense.id, ]; - // keep the original date, only update the day - expense.payment.date.setDate(input.payment.date.getDate()); + // keep the original date, only update the day (create new Date to avoid mutation) + const updatedDate = new Date(expense.payment.date); + updatedDate.setDate(input.payment.date.getDate()); + const updatedExpense = { ...expense, ...payload, payment: { ...expense.payment, ...payload.payment, - date: expense.payment.date, + date: updatedDate, }, }; @@ -228,8 +230,13 @@ export default class ExpenseService { if (!res.ok) { throw new Deno.errors.Interrupted("Failed to update expense"); } + + return updatedExpense; }); + // Wait for all updates to complete + await Promise.all(updatePromises); + const updatedExpense = { ...rawExpense.value, ...input, @@ -321,8 +328,8 @@ export default class ExpenseService { (e) => e.payment.date.getMonth() + 1 >= initialMonth, ); } - // TODO make this more atomic. if one fails, all should fail - expensesToDelete.forEach(async (expense) => { + // Delete all expenses atomically - if one fails, all fail + const deletePromises = expensesToDelete.map(async (expense) => { const expenseKey = [Keys.EXPENSES, userId, expense.id]; const correlationKey = [ Keys.EXPENSES_BY_CORRELATION, @@ -352,8 +359,13 @@ export default class ExpenseService { `Failed to delete expense. Expense ID: ${expense.id}`, ); } + + return expense; }); + // Wait for all deletes to complete + await Promise.all(deletePromises); + return rawExpense.value; } } diff --git a/utils/expenses/factory.ts b/utils/expenses/factory.ts index 97a7131..194be36 100644 --- a/utils/expenses/factory.ts +++ b/utils/expenses/factory.ts @@ -98,11 +98,13 @@ export default class ExpenseInputFactory { } } - private getInstallmentDate(installment: number) { - return installment === 1 ? new Date(this.data.paymentDate) : new Date( - new Date(this.data.paymentDate).setMonth( - new Date(this.data.paymentDate).getMonth() + installment - 1, - ), - ); + private getInstallmentDate(installment: number): Date { + if (installment === 1) { + return new Date(this.data.paymentDate); + } + + const date = new Date(this.data.paymentDate); + date.setMonth(date.getMonth() + installment - 1); + return date; } }