Skip to content

feat(DJ-92): account summary endpoint and Angular dashboard - #31

Open
devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1786976886-account-summary-dashboard
Open

feat(DJ-92): account summary endpoint and Angular dashboard#31
devin-ai-integration[bot] wants to merge 6 commits into
mainfrom
devin/1786976886-account-summary-dashboard

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Summary

DJ-92: adds an aggregated account overview — a new authenticated backend endpoint plus an Angular /dashboard page that consumes it. Backend and frontend were built in parallel against a contract locked up front (below); the frontend's temporary mock was removed during integration, so the shipped page calls the real endpoint.

Locked contract (single source of truth)

GET /api/account-summary, authenticated only, 200 application/json:

{
  "totalBalance": 12345.67,
  "accountCount": 3,
  "operationCount": 42,
  "accounts": [ { "id": 1, "name": "Current account", "balance": 4200.00, "operationCount": 17 } ],
  "recentOperations": [ { "id": 9, "date": "2026-08-01T10:15:30Z", "description": "Groceries", "amount": -54.20, "bankAccountName": "Current account" } ]
}

Rules: recentOperations = the 10 most recent Operation rows by date DESC across all accounts; monetary values are BigDecimal scale 2; date is ISO-8601 UTC (Instant); an empty database returns totalBalance 0, accountCount 0, operationCount 0 and empty arrays (never null).

Backend

AccountSummaryResourceAccountSummaryService → two set-based JPQL queries, so per-account counts are one grouped query rather than one query per account:

// OperationRepository
@Query("select operation.bankAccount.id, count(operation) from Operation operation where operation.bankAccount is not null group by operation.bankAccount.id")
List<Object[]> countByBankAccount();

@Query("select operation from Operation operation left join fetch operation.bankAccount order by operation.date desc")
List<Operation> findRecentOperations(Pageable pageable);   // PageRequest.of(0, 10)

Scale-2 rounding and never-null collections are enforced in the DTOs themselves (setters normalize), so the contract holds regardless of caller. No security annotation on the resource: SecurityConfiguration already gates /api/** with .authenticated(), which is what produces the 401 asserted by the IT.

Frontend

app/dashboard/ standalone component (signals + inject()), lazy-loaded route guarded by UserRouteAccessService with authorities: [], plus navbar and home entry points and i18n/en/dashboard.json. Currency via Angular currency pipe, dates via the shared FormatMediumDatetimePipe.

Verification

Gates green: ./mvnw verify, ./npmw test, ./npmw run lint, ./mvnw -Pprod clean verify. AccountSummaryResourceIT covers aggregation figures + the 10-row DESC limit, the empty-DB zeros/empty-arrays case, and the anonymous 401. Verified manually against seeded H2 data (2 accounts, 12 operations): raw JSON totals and the rendered page agree ($3,580.00, 2 accounts, 12 operations, per-account 5/7), and an anonymous visit to /dashboard redirects to login.

Before (no dashboard) After (populated dashboard)
before after

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/631b9c1e27864c639552a1b92cf279ee

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines +26 to +30
this.dashboardService.getAccountSummary().subscribe({
next: summary => this.accountSummary.set(summary),
complete: () => this.isLoading.set(false),
error: () => this.isLoading.set(false),
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Dashboard page shows nothing at all when the data request fails

When the dashboard data request fails the page silently stops showing the loading text (error: () => this.isLoading.set(false) at src/main/webapp/app/dashboard/dashboard.ts:29) without ever showing an error message, so users see an empty page with no explanation.
Impact: A backend outage or an expired session makes the dashboard look permanently blank instead of telling the user something went wrong.

Template only renders content when a summary object exists

src/main/webapp/app/dashboard/dashboard.html:4-6 renders the loading paragraph while isLoading() is true and otherwise only renders content when accountSummary() is non-null. On the error path the signal stays null and isLoading becomes false, so both branches are skipped and only the <h2> heading remains. Other pages in the app surface failures (e.g. via alert service / error handling); the dashboard has no such fallback.

Prompt for agents
The dashboard component (src/main/webapp/app/dashboard/dashboard.ts) subscribes to DashboardService.getAccountSummary() and, on error, only clears the loading flag. Because src/main/webapp/app/dashboard/dashboard.html renders content only when accountSummary() is non-null, an HTTP failure leaves the page blank with no feedback. Consider tracking an error state signal (or surfacing the failure through the app's existing alert/error handling) and adding an error branch to the template with a translated message in src/main/webapp/i18n/en/dashboard.json.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed: on the error path accountSummary() stays null and isLoading() becomes false, so dashboard.html renders neither branch and only the heading remains. Worth fixing with an error signal plus a translated message branch; holding the change until the in-flight end-to-end test run finishes so it doesn't shift the revision under test.

Comment on lines +29 to +54
public AccountSummaryDTO getSummary() {
List<Object[]> accountRows = bankAccountRepository.findSummaryData();
Map<Long, Long> operationCounts = operationRepository
.countByBankAccount()
.stream()
.collect(Collectors.toMap(row -> (Long) row[0], row -> (Long) row[1]));
List<AccountBalanceDTO> accounts = accountRows
.stream()
.map(row ->
new AccountBalanceDTO((Long) row[0], (String) row[1], (BigDecimal) row[2], operationCounts.getOrDefault((Long) row[0], 0L))
)
.toList();

AccountSummaryDTO summary = new AccountSummaryDTO();
summary.setTotalBalance(
accountRows
.stream()
.map(row -> (BigDecimal) row[2])
.reduce(BigDecimal.ZERO, BigDecimal::add)
);
summary.setAccountCount(accounts.size());
summary.setOperationCount(operationRepository.countAllOperations());
summary.setAccounts(accounts);
summary.setRecentOperations(
operationRepository.findRecentOperations(PageRequest.of(0, 10)).stream().map(RecentOperationDTO::new).toList()
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟨 Dashboard endpoint exposes every user's account balances and operations to any authenticated user

The new endpoint aggregates all bank accounts and all operations globally (bankAccountRepository.findSummaryData() / operationRepository.findRecentOperations(...) at src/main/java/io/github/jhipster/sample/service/AccountSummaryService.java:30-54) with no filtering by the current user, while the UI presents it as the signed-in user's personal dashboard (src/main/webapp/app/home/home.html:20). Any authenticated (non-admin) user therefore sees other users' account names, balances, operation descriptions and amounts.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accurate description of the behaviour, but it matches the locked contract for this ticket, which specifies aggregation "across all accounts" with no per-user scoping — and it mirrors the existing app: BankAccountResource.getAllBankAccounts() also returns every account to any authenticated caller (no user filter anywhere in that resource), so the dashboard is not widening exposure beyond what the entity screens already show.

BankAccount does have a user relation, so scoping is feasible (filter findSummaryData() / findRecentOperations() by SecurityUtils.getCurrentUserLogin()), but that would be a deliberate change to the agreed contract and to the app's existing visibility model. Flagging it for a decision rather than changing it unilaterally.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

✅ E2E tested locally (Devin)

Ran the app locally (Spring Boot dev/H2 on :8080, Angular via Browsersync on :9000), created data through the existing Bank Account / Operation entity screens, and cross-checked /dashboard against the raw GET /api/account-summary payload. All checks passed.

Figures match data created via entity screens (click to collapse)

Starting from the faker seed (10 accounts / 10 operations / $164,539.81) I created ZZ-Alpha ($1,000.50) and ZZ-Beta ($250.25) plus 3 operations (-45.75, 12.30, 99.99):

Dashboard cards and per-account balances

  • Total balance $165,790.56 = 164,539.81 + 1,000.50 + 250.25
  • Accounts 12, Operations 13
  • Per-account operation counts: ZZ-Alpha 2, ZZ-Beta 1, seeded zero-operation accounts 0

Recent operations: exactly 10 rows even though 13 operations exist, sorted date DESC across accounts, negative amount rendered -$45.75, 2-decimal formatting held:

Recent operations table capped at 10 and ordered DESC

Raw API matched the page field-for-field: totalBalance 165790.56, accountCount 12, operationCount 13, recentOperations length 10, and monetary values keep scale 2 in the payload ("amount":12.30, "balance":21642.40).

Empty database → zeros + empty states

Restarted the backend with --spring.liquibase.contexts=dev (empty in-memory H2). API returned {"accountCount":0,"accounts":[],"operationCount":0,"recentOperations":[],"totalBalance":0.00} (never null) and the page rendered cleanly:

Empty dashboard state

Auth gating

Logged out, the navbar Dashboard item and the home-page button are gone, and direct navigation to /dashboard redirects to Sign in; anonymous curl /api/account-summary returns HTTP/1.1 401.

Logged out home page without Dashboard entry
Anonymous /dashboard redirects to Sign in

i18n / console

No raw dashboard.*, global.menu.dashboard or home.dashboard.link keys rendered anywhere; no console errors on the dashboard. One pre-existing NG0951 error appears on the login page (LoginComponent.ngAfterViewInit), unrelated to this PR.

Minor note: seeded operations with no bank account render an empty "Bank account" cell (allowed by the contract, but a dash/placeholder might read better).

Not covered: non-admin (user) access, non-English locales, narrow/mobile viewport.

Written by Devin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants