Description
Priority P1
Type / Area feature, backend, budgets, analytics
Suggested Complexity High
Summary src/budgets/budgets.service.ts and src/analytics/analytics.service.ts are both status-only scaffolds. There are also two analytics modules (src/analytics/ and src/analytics-system/) with no distinction between them. Build BudgetsService with full CRUD backed by a new budgets table, and build AnalyticsService with real queries against the existing transactions table — spending trend, category breakdown, and budget-vs-actual. Consolidate the two analytics modules into one.
Problem Both services are identical scaffolds:
typescript
// src/budgets/budgets.service.ts — current state
status(): { module: string; status: string } {
return { module: 'budgets', status: 'ready' };
}
// src/analytics/analytics.service.ts — current state
status(): { module: string; status: string } {
return { module: 'analytics', status: 'ready' };
}
Additionally src/analytics-system/analytics-system.service.ts is an identical third scaffold with no documented purpose distinct from src/analytics/. The duplication exists because a second analytics module was generated without a clear responsibility boundary — a DEAD_CODE_TICKETS entry was flagged for this exact issue in the contracts repo's companion doc. The stellarspend-app analytics page calls GET /analytics/trend, GET /analytics/category-breakdown, and GET /analytics/budget-vs-actual — all three return { module: 'analytics', status: 'ready' } today.
Scope
In Scope
New migration: src/migrations/1710000000004-create-budgets-table.ts — budgets(id uuid PK, user_id uuid NOT NULL, name varchar(80), asset varchar(12), amount numeric(30,12), category varchar(80), period varchar(20) DEFAULT 'monthly', start_date date, end_date date, created_at timestamptz, updated_at timestamptz)
New entity: src/budgets/entities/budget.entity.ts
BudgetsService: create, findAll(userId), findOne(userId, id), update(userId, id, dto), remove(userId, id) — standard CRUD with ownership checks
AnalyticsService:
getSpendingTrend(userId: string, asset: string, weeks: number): Promise<{ week: string; total: string }[]> — groups transactions by ISO week using PostgreSQL date_trunc
getCategoryBreakdown(userId: string, asset: string, since: Date): Promise<{ category: string; total: string; percentage: string }[]> — sums by category, computes percentage of grand total in SQL
getBudgetVsActual(userId: string): Promise<{ budgetName: string; budgeted: string; actual: string; variance: string }[]> — joins budgets to transactions on category and asset, computes variance
Delete src/analytics-system/ entirely — move any unique content to src/analytics/ and remove the module from app.module.ts
Wire BudgetsService and AnalyticsService into their controllers with @UseGuards(JwtAuthGuard)
Unit tests for both services
Out of Scope
Changing the TransactionEntity schema
Real-time streaming analytics
Files src/budgets/budgets.service.ts (rewrite) src/budgets/budgets.controller.ts (rewrite) src/budgets/budgets.module.ts (modify) src/budgets/entities/budget.entity.ts (new) src/analytics/analytics.service.ts (rewrite) src/analytics/analytics.controller.ts (rewrite) src/analytics/analytics.module.ts (modify) src/analytics-system/ (delete entire folder) src/app.module.ts (modify — remove AnalyticsSystemModule) src/budgets/budgets.service.spec.ts (new) src/analytics/analytics.service.spec.ts (new) src/migrations/1710000000004-create-budgets-table.ts (new)
Acceptance Criteria
Full CRUD on /budgets with ownership validation
GET /analytics/trend?asset=USDC&weeks=8 returns 8 weekly buckets with real aggregated totals from the transactions table
GET /analytics/category-breakdown?asset=XLM&since=2026-01-01 returns categories with percentage computed in SQL
GET /analytics/budget-vs-actual returns per-budget variance using real spend data
src/analytics-system/ folder no longer exists and app.module.ts does not import AnalyticsSystemModule
New migration runs cleanly
npm test -- budgets and npm test -- analytics both pass
How to Test Create a budget (POST /budgets) for "Groceries" 200 USDC monthly. Sync transactions containing USDC Groceries spend. Call GET /analytics/budget-vs-actual — verify the Groceries budget row shows real spend vs 200 USDC. Call GET /analytics/trend?asset=USDC&weeks=4 — verify 4 weekly rows. Call GET /analytics/category-breakdown?asset=USDC — verify percentages sum to 100%.
PR Requirements
CI checks must pass
npm test passes
Include screenshots of test results
Link issue using Closes #ISSUE_NUMBER
Project Links Repository: https://github.com/stellarspend/stellarspend-api Community: https://t.me/+7RpAgpVggOphNzJk
Description
Priority P1
Type / Area feature, backend, budgets, analytics
Suggested Complexity High
Summary src/budgets/budgets.service.ts and src/analytics/analytics.service.ts are both status-only scaffolds. There are also two analytics modules (src/analytics/ and src/analytics-system/) with no distinction between them. Build BudgetsService with full CRUD backed by a new budgets table, and build AnalyticsService with real queries against the existing transactions table — spending trend, category breakdown, and budget-vs-actual. Consolidate the two analytics modules into one.
Problem Both services are identical scaffolds:
typescript
// src/budgets/budgets.service.ts — current state
status(): { module: string; status: string } {
return { module: 'budgets', status: 'ready' };
}
// src/analytics/analytics.service.ts — current state
status(): { module: string; status: string } {
return { module: 'analytics', status: 'ready' };
}
Additionally src/analytics-system/analytics-system.service.ts is an identical third scaffold with no documented purpose distinct from src/analytics/. The duplication exists because a second analytics module was generated without a clear responsibility boundary — a DEAD_CODE_TICKETS entry was flagged for this exact issue in the contracts repo's companion doc. The stellarspend-app analytics page calls GET /analytics/trend, GET /analytics/category-breakdown, and GET /analytics/budget-vs-actual — all three return { module: 'analytics', status: 'ready' } today.
Scope
In Scope
New migration: src/migrations/1710000000004-create-budgets-table.ts — budgets(id uuid PK, user_id uuid NOT NULL, name varchar(80), asset varchar(12), amount numeric(30,12), category varchar(80), period varchar(20) DEFAULT 'monthly', start_date date, end_date date, created_at timestamptz, updated_at timestamptz)
New entity: src/budgets/entities/budget.entity.ts
BudgetsService: create, findAll(userId), findOne(userId, id), update(userId, id, dto), remove(userId, id) — standard CRUD with ownership checks
AnalyticsService:
getSpendingTrend(userId: string, asset: string, weeks: number): Promise<{ week: string; total: string }[]> — groups transactions by ISO week using PostgreSQL date_trunc
getCategoryBreakdown(userId: string, asset: string, since: Date): Promise<{ category: string; total: string; percentage: string }[]> — sums by category, computes percentage of grand total in SQL
getBudgetVsActual(userId: string): Promise<{ budgetName: string; budgeted: string; actual: string; variance: string }[]> — joins budgets to transactions on category and asset, computes variance
Delete src/analytics-system/ entirely — move any unique content to src/analytics/ and remove the module from app.module.ts
Wire BudgetsService and AnalyticsService into their controllers with @UseGuards(JwtAuthGuard)
Unit tests for both services
Out of Scope
Changing the TransactionEntity schema
Real-time streaming analytics
Files src/budgets/budgets.service.ts (rewrite) src/budgets/budgets.controller.ts (rewrite) src/budgets/budgets.module.ts (modify) src/budgets/entities/budget.entity.ts (new) src/analytics/analytics.service.ts (rewrite) src/analytics/analytics.controller.ts (rewrite) src/analytics/analytics.module.ts (modify) src/analytics-system/ (delete entire folder) src/app.module.ts (modify — remove AnalyticsSystemModule) src/budgets/budgets.service.spec.ts (new) src/analytics/analytics.service.spec.ts (new) src/migrations/1710000000004-create-budgets-table.ts (new)
Acceptance Criteria
Full CRUD on /budgets with ownership validation
GET /analytics/trend?asset=USDC&weeks=8 returns 8 weekly buckets with real aggregated totals from the transactions table
GET /analytics/category-breakdown?asset=XLM&since=2026-01-01 returns categories with percentage computed in SQL
GET /analytics/budget-vs-actual returns per-budget variance using real spend data
src/analytics-system/ folder no longer exists and app.module.ts does not import AnalyticsSystemModule
New migration runs cleanly
npm test -- budgets and npm test -- analytics both pass
How to Test Create a budget (POST /budgets) for "Groceries" 200 USDC monthly. Sync transactions containing USDC Groceries spend. Call GET /analytics/budget-vs-actual — verify the Groceries budget row shows real spend vs 200 USDC. Call GET /analytics/trend?asset=USDC&weeks=4 — verify 4 weekly rows. Call GET /analytics/category-breakdown?asset=USDC — verify percentages sum to 100%.
PR Requirements
CI checks must pass
npm test passes
Include screenshots of test results
Link issue using Closes #ISSUE_NUMBER
Project Links Repository: https://github.com/stellarspend/stellarspend-api Community: https://t.me/+7RpAgpVggOphNzJk