Skip to content

Commit 875047e

Browse files
fix: resolve backend property mismatches and implement balance reversal
- fix(backend): correct activity controller to use 'sub' instead of 'id' from JWT - fix(backend): implement automatic balance reversal when an expense is deleted - fix(backend): fix expense calculator bug affecting participants who paid more than they owed - fix(backend): add validation to ensure total payments match the expense amount - fix(mobile): update SkeletonList colors to properly support dark mode - chore: update .gitignore to exclude .expo, build artifacts, and logs
1 parent a20678b commit 875047e

8 files changed

Lines changed: 562 additions & 55 deletions

File tree

.gitignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ coverage
1010
.agents
1111
.agent
1212
.turbo
13+
.expo
14+
node_modules
15+
dist
16+
.next
17+
.next/
18+
out/
19+
build/
20+
*.tsbuildinfo
21+
*.log
22+
.DS_Store
23+
.idea
24+
.vscode
1325
skills-lock.json
1426
*.pem
1527
*.key

apps/backend/.turbo/turbo-test.log

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,47 @@
22
> backend@1.0.0 test D:\ak\project\FairShare\apps\backend
33
> jest --runInBand --passWithNoTests
44

5-
PASS src/settlements/settlements.integration.spec.ts (30.803 s)
6-
(node:22644) NOTE: The AWS SDK for JavaScript (v2) has reached end-of-support.
7-
It will no longer receive updates or releases.
8-
9-
Please migrate your code to use AWS SDK for JavaScript (v3).
10-
For more information, check the blog post at https://a.co/cUPnyil
11-
(Use `node --trace-warnings ...` to show where the warning was created)
12-
PASS src/receipts/receipts.service.spec.ts
5+
PASS src/receipts/receipts.integration.spec.ts (27.096 s)
6+
PASS src/payments/payments.service.spec.ts
7+
[Nest] 27236 - 03/25/2026, 11:37:32 PM  LOG [PaymentsService] Payment succeeded and settlement recorded paymentId=payment-1
8+
[Nest] 27236 - 03/25/2026, 11:37:32 PM  LOG [PaymentsService] Payment succeeded and settlement recorded paymentId=payment-1
9+
[Nest] 27236 - 03/25/2026, 11:37:32 PM  LOG [PaymentsService] Payment succeeded and settlement recorded paymentId=payment-1
1310
PASS src/expenses/expenses.integration.spec.ts
11+
PASS src/groups/groups.service.spec.ts
1412
PASS src/expenses/expenses.service.spec.ts
1513
PASS src/settlements/settlements.service.spec.ts
16-
PASS src/groups/groups.service.spec.ts
14+
PASS src/settlements/settlements.integration.spec.ts
15+
[Nest] 27236 - 03/25/2026, 11:37:42 PM  ERROR [NotificationsService] Expo push send failed
16+
Error: transient failure
17+
at Object.<anonymous> (D:\ak\project\FairShare\apps\backend\src\notifications\notifications.service.spec.ts:98:30)
18+
at Promise.then.completed (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\utils.js:298:28)
19+
at new Promise (<anonymous>)
20+
at callAsyncCircusFn (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\utils.js:231:10)
21+
at _callCircusTest (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\run.js:316:40)
22+
at runNextTicks (node:internal/process/task_queues:64:5)
23+
at processImmediate (node:internal/timers:472:9)
24+
at async _runTest (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\run.js:252:3)
25+
at async _runTestsForDescribeBlock (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\run.js:126:9)
26+
at async _runTestsForDescribeBlock (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\run.js:121:9)
27+
at async run (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\run.js:71:3)
28+
at async runAndTransformResultsToJestFormat (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\legacy-code-todo-rewrite\jestAdapterInit.js:122:21)
29+
at async jestAdapter (D:\ak\project\FairShare\node_modules\.pnpm\jest-circus@29.7.0\node_modules\jest-circus\build\legacy-code-todo-rewrite\jestAdapter.js:79:19)
30+
at async runTestInternal (D:\ak\project\FairShare\node_modules\.pnpm\jest-runner@29.7.0\node_modules\jest-runner\build\runTest.js:367:16)
31+
at async runTest (D:\ak\project\FairShare\node_modules\.pnpm\jest-runner@29.7.0\node_modules\jest-runner\build\runTest.js:444:34)
1732
PASS src/notifications/notifications.service.spec.ts
33+
[Nest] 27236 - 03/25/2026, 11:37:42 PM  LOG [NotificationsService] {"event":"notification_event_queued","channel":"fairshare:notifications","payload":{"userIds":["user-1"],"payload":{"type":"expense_created","title":"Expense added","body":"Dinner was added"},"queuedAt":"2026-03-25T18:07:42.461Z"}}
34+
[Nest] 27236 - 03/25/2026, 11:37:42 PM  WARN [NotificationsService] {"event":"notification_invalid_tokens_removed","count":1}
35+
[Nest] 27236 - 03/25/2026, 11:37:42 PM  LOG [NotificationsService] {"event":"notification_push_sent","ticketCount":2}
36+
[Nest] 27236 - 03/25/2026, 11:37:42 PM  LOG [NotificationsService] {"event":"notification_push_sent","ticketCount":1}
1837
PASS src/balances/balances.service.spec.ts
1938
PASS src/users/users.service.spec.ts
2039
PASS src/simplify/simplify.service.spec.ts
40+
PASS src/receipts/receipts.service.spec.ts
2141
PASS src/activity/activity.service.spec.ts
2242
PASS src/expenses/expense-calculator.spec.ts
2343

24-
Test Suites: 12 passed, 12 total
25-
Tests: 16 passed, 16 total
44+
Test Suites: 14 passed, 14 total
45+
Tests: 30 passed, 30 total
2646
Snapshots: 0 total
27-
Time: 38.565 s
47+
Time: 45.038 s
2848
Ran all test suites.

apps/backend/src/activity/activity.controller.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { Controller, Get, Param, Query, UseGuards, Request } from '@nestjs/common';
1+
import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common';
22
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
3+
import { CurrentUser } from '../common/decorators/current-user.decorator';
4+
import { JwtPayload } from '../auth/types/auth.types';
35
import { ActivityService } from './activity.service';
46

57
@Controller('activity')
@@ -8,8 +10,8 @@ export class ActivityController {
810
constructor(private readonly activityService: ActivityService) {}
911

1012
@Get()
11-
getUserActivity(@Request() req: any, @Query('cursor') cursor = '0', @Query('limit') limit = '20') {
12-
return this.activityService.getUserActivity(req.user.id, Number(cursor), Number(limit));
13+
getUserActivity(@CurrentUser() user: JwtPayload, @Query('cursor') cursor = '0', @Query('limit') limit = '20') {
14+
return this.activityService.getUserActivity(user.sub, Number(cursor), Number(limit));
1315
}
1416

1517
@Get('group/:id')

apps/backend/src/expenses/expense-calculator.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CreateExpenseSplitInputDto } from './dto/create-expense.dto';
1+
import { CreateExpenseSplitInputDto } from './dto/create-expense.dto';
22

33
export interface BalanceDelta {
44
userId: string;
@@ -18,7 +18,7 @@ export const calculateBalanceDeltas = (payerId: string, splits: CreateExpenseSpl
1818
const paid = BigInt(split.paidAmountCents);
1919
const net = owed - paid;
2020

21-
if (net > 0n) {
21+
if (net !== 0n) {
2222
deltas.push({
2323
userId: split.userId,
2424
counterpartyUserId: payerId,

apps/backend/src/expenses/expenses.service.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ export class ExpensesService {
3636
}
3737

3838
const owedSum = sumMoney(dto.splits.map((split) => split.owedAmountCents));
39+
const paidSum = sumMoney(dto.splits.map((split) => split.paidAmountCents));
3940
try {
40-
assertMoneyEquality(owedSum, totalAmount, 'Split sum must equal total amount');
41-
} catch {
42-
throw new BadRequestException('Split sum must equal total amount');
41+
assertMoneyEquality(owedSum, totalAmount, 'Total owed sum must equal total amount');
42+
assertMoneyEquality(paidSum, totalAmount, 'Total paid sum must equal total amount');
43+
} catch (e: any) {
44+
throw new BadRequestException(e.message);
4345
}
4446

4547
if (idempotencyKey) {
@@ -230,12 +232,34 @@ export class ExpensesService {
230232
}
231233

232234
async remove(id: string, actorUserId: string): Promise<{ success: true }> {
233-
const expense = await this.prisma.expense.findUnique({ where: { id } });
235+
const expense = await this.prisma.expense.findUnique({
236+
where: { id },
237+
include: { splits: true },
238+
});
234239
if (!expense) {
235240
throw new NotFoundException('Expense not found');
236241
}
237242

238243
await this.prisma.$transaction(async (tx) => {
244+
const deltas = calculateBalanceDeltas(
245+
expense.payerId,
246+
expense.splits.map((s) => ({
247+
userId: s.userId,
248+
owedAmountCents: s.owedAmountCents.toString(),
249+
paidAmountCents: s.paidAmountCents.toString(),
250+
})),
251+
);
252+
253+
for (const delta of deltas) {
254+
await this.balancesService.adjustBalance(
255+
tx as unknown as Prisma.TransactionClient,
256+
expense.groupId,
257+
delta.userId,
258+
delta.counterpartyUserId,
259+
-delta.delta,
260+
);
261+
}
262+
239263
await tx.split.deleteMany({ where: { expenseId: id } });
240264
await tx.expense.delete({ where: { id } });
241265
await tx.activity.create({

0 commit comments

Comments
 (0)