ali-ahnaf / ali-ahnaf/pocket_pixel

πŸ§™ XP & Level System - Backend

Open
#149 0 comments 0 reactions 0 assignees View on GitHub
backend blocked epic
Dominant language
TypeScript
Stars
14
Forks
91
PR merge metrics
No merged PRs in 30d

Description

## πŸ† Context / Background

Pocket Pixel is a personal finance tracker. To encourage healthy financial habits, we're adding a **gamification layer** β€” a **XP (Experience Points) and Level system** that rewards users for reaching financial goals.

This issue covers the **backend implementation**: the data model, achievement triggers, XP tracking, and the API endpoints to expose progress to the frontend.

> πŸ”— The frontend counterpart (XP bars, level display, achievement notifications) is tracked in a separate issue. The frontend issue is **blocked by this one**.

---

## 🎯 Problem / Goal

Users need positive reinforcement to build good financial habits. An XP and level system turns routine tasks (logging daily expenses, staying within budget) into rewarding milestones β€” making the app feel like an RPG progression system.

---

## ✨ Feature Description

### XP & Level Model

Create a `UserProgress` entity that tracks a user's XP and derived level:

```typescript
// entities/user-progress.entity.ts
UserProgress {
id: number
userId: number // FK β†’ User
totalXp: number // cumulative XP earned
level: number // derived from totalXp (see formula below)
createdAt, updatedAt, deletedAt
}
```

**Level formula** (simple threshold ladder):

| Level | XP Required |
|-------|-------------|
| 1 | 0 |
| 2 | 200 |
| 3 | 500 |
| 4 | 1 000 |
| 5 | 2 000 |
| 6 | 3 500 |
| 7 | 5 500 |
| 8 | 8 000 |
| 9 | 11 000 |
| 10 | 15 000 |

Recalculate `level` whenever `totalXp` changes.

---

### Achievement Triggers (5 defined)

Create an `Achievement` entity and an `EarnedAchievement` join entity:

| # | Key | Name | Description | XP Reward |
|---|-----|------|-------------|-----------|
| 1 | `BUDGET_MASTER` | Budget Master | Stay within budget across **all categories** for a full calendar month | **500 XP** |
| 2 | `DAILY_LOGGER_7` | Daily Adventurer | Log at least one expense every day for **7 consecutive days** | **200 XP** |
| 3 | `DAILY_LOGGER_30` | Consistent Chronicler | Log at least one expense every day for **30 consecutive days** | **750 XP** |
| 4 | `SAVINGS_CHAMPION` | Savings Champion | Spend **20% or less** of the monthly budget in any single category for 3 consecutive months | **400 XP** |
| 5 | `CATEGORY_CONQUEROR` | Category Conqueror | Stay within budget in every individual spending category for a month | **300 XP** |

Each achievement can only be earned **once per qualifying period** (e.g., `BUDGET_MASTER` can be earned every month it is achieved β€” not just once ever).

---

### Achievement Evaluation

Achievements should be evaluated:
- **After a transaction is created or updated** β€” re-check streak-based achievements (`DAILY_LOGGER_*`)
- **On a nightly cron job** β€” re-check budget/savings achievements for the current month

Use the existing scheduler pattern in `packages/api/src/scheduler/recurring-scheduler.ts` for the cron job.

---

### API Endpoints

```
GET /api/users/:userId/progress β†’ { totalXp, level, nextLevelXp, earnedAchievements[] }
GET /api/users/:userId/achievements β†’ list of all achievements with earned status + date
```

---

### Shared DTOs

Add to `packages/shared/src/contracts/gamification.ts`:

```typescript
export interface UserProgressResponse {
totalXp: number;
level: number;
nextLevelXp: number | null; // null if max level
earnedAchievements: EarnedAchievementDto[];
}

export interface EarnedAchievementDto {
key: string;
name: string;
description: string;
xpReward: number;
earnedAt: string; // ISO date
}

export interface AchievementDto extends EarnedAchievementDto {
earned: boolean;
}
```

Re-export from `contracts/index.ts` β†’ `packages/shared/src/index.ts`. Run `npm run build:shared`.

---

## πŸ—‚ Suggested Approach

### Files to Create / Modify
- `packages/shared/src/contracts/gamification.ts` β€” new shared DTOs
- `packages/api/src/entities/user-progress.entity.ts` β€” new entity
- `packages/api/src/entities/achievement.entity.ts` β€” achievement definitions
- `packages/api/src/entities/earned-achievement.entity.ts` β€” join entity
- `packages/api/src/repositories/gamification.repository.ts` β€” DB queries
- `packages/api/src/services/gamification.service.ts` β€” XP logic, level calc, achievement checks
- `packages/api/src/routes/gamification/` β€” route files
- `packages/api/src/routes/gamification.routes.ts` β€” aggregator
- `packages/api/src/scheduler/recurring-scheduler.ts` β€” register nightly achievement evaluation

### Steps
1. Create the entities and generate a migration: `npm run migration:generate -- -n AddGamification`
2. Run the migration: `npm run migration:run`
3. Seed the 5 achievement rows (in a seeder or migration)
4. Implement `gamification.service.ts` β€” XP award, level recalculation, each achievement check
5. Hook achievement evaluation into the transaction creation flow
6. Register the nightly cron in the scheduler
7. Create the two GET routes
8. Run Prettier on all changed files

---

## βœ… Acceptance Criteria

- [ ] `UserProgress`, `Achievement`, and `EarnedAchievement` entities exist with migrations applied
- [ ] All 5 achievement triggers are implemented with correct XP rewards
- [ ] `GET /api/users/:userId/progress` returns current XP, level, and earned achievements
- [ ] `GET /api/users/:userId/achievements` returns all achievements with earned status
- [ ] Achievement evaluation runs after transaction creation and nightly via cron
- [ ] Levels are recalculated correctly using the threshold table above
- [ ] Shared DTOs live in `packages/shared` and are imported via `@expense-tracker/shared`
- [ ] No `try/catch` in routes β€” errors handled by global error handler via `asyncHandler`
- [ ] No TypeScript `any` types
- [ ] Code formatted with Prettier

---

## 🧭 First-Time Contributor Guide

**New to this repo? Start here:**

1. **Clone and install**: Follow the README to get the app running locally
2. **Run the API**: `npm run dev:api` β€” runs on `http://localhost:4000`
3. **Study existing entities**: look at `packages/api/src/entities/` to understand the `BaseEntity` pattern (all entities extend it for `createdAt`, `updatedAt`, `softDelete`)
4. **Study existing services**: `packages/api/src/services/transactions.service.ts` is a good example of business logic with repository injection
5. **Understand the scheduler**: read `packages/api/src/scheduler/recurring-scheduler.ts` to see how cron jobs are registered
6. **Generate migrations**: after creating/editing entities, run `npm run migration:generate -- -n ` then `npm run migration:run`
7. **Test your endpoints**: use `curl` or Postman with a Bearer token
8. **Run tests**: `npm run test:api`
9. **Run Prettier after every file**: `npx prettier --write `

If you get stuck, feel free to ask questions in the issue comments!

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.