feat: Implement task and reward tracking feature
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 24s
All checks were successful
Gitea Actions Demo / build-and-push (push) Successful in 24s
- Added tracking events for tasks, penalties, and rewards with timestamps. - Created new TinyDB table for tracking records to maintain audit history. - Developed backend API for querying tracking events with filters and pagination. - Implemented logging for tracking events with per-user rotating log files. - Added unit tests for tracking event creation, querying, and anonymization. - Deferred frontend changes for future implementation. - Established acceptance criteria and documentation for the tracking feature. feat: Introduce account deletion scheduler - Implemented a scheduler to delete accounts marked for deletion after a configurable threshold. - Added new fields to the User model to manage deletion status and attempts. - Created admin API endpoints for managing deletion thresholds and viewing the deletion queue. - Integrated error handling and logging for the deletion process. - Developed unit tests for the deletion scheduler and related API endpoints. - Documented the deletion process and acceptance criteria.
This commit is contained in:
149
.github/specs/active/feat-dynamic-points/IMPLEMENTATION_SUMMARY.md
vendored
Normal file
149
.github/specs/active/feat-dynamic-points/IMPLEMENTATION_SUMMARY.md
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
# Tracking Feature Implementation Summary
|
||||
|
||||
## ✅ Implementation Complete
|
||||
|
||||
All acceptance criteria from [feat-tracking.md](.github/specs/active/feat-dynamic-points/feat-tracking.md) have been implemented and tested.
|
||||
|
||||
---
|
||||
|
||||
## 📦 What Was Delivered
|
||||
|
||||
### Backend
|
||||
|
||||
1. **Data Model** ([tracking_event.py](backend/models/tracking_event.py))
|
||||
- `TrackingEvent` dataclass with full type safety
|
||||
- Factory method `create_event()` for server-side timestamp generation
|
||||
- Delta invariant validation (`delta == points_after - points_before`)
|
||||
|
||||
2. **Database Layer** ([tracking.py](backend/db/tracking.py))
|
||||
- New TinyDB table: `tracking_events.json`
|
||||
- Helper functions: `insert_tracking_event`, `get_tracking_events_by_child`, `get_tracking_events_by_user`, `anonymize_tracking_events_for_user`
|
||||
- Offset-based pagination with sorting by `occurred_at` (desc)
|
||||
|
||||
3. **Audit Logging** ([tracking_logger.py](backend/utils/tracking_logger.py))
|
||||
- Per-user rotating file handlers (`logs/tracking_user_<user_id>.log`)
|
||||
- 10MB max file size, 5 backups
|
||||
- Structured log format with all event metadata
|
||||
|
||||
4. **API Integration** ([child_api.py](backend/api/child_api.py))
|
||||
- Tracking added to:
|
||||
- `POST /child/<id>/trigger-task` → action: `activated`
|
||||
- `POST /child/<id>/request-reward` → action: `requested`
|
||||
- `POST /child/<id>/trigger-reward` → action: `redeemed`
|
||||
- `POST /child/<id>/cancel-request-reward` → action: `cancelled`
|
||||
|
||||
5. **Admin API** ([tracking_api.py](backend/api/tracking_api.py))
|
||||
- `GET /admin/tracking` with filters:
|
||||
- `child_id` (required if no `user_id`)
|
||||
- `user_id` (admin only)
|
||||
- `entity_type` (task|reward|penalty)
|
||||
- `action` (activated|requested|redeemed|cancelled)
|
||||
- `limit` (default 50, max 500)
|
||||
- `offset` (default 0)
|
||||
- Returns total count for future pagination UI
|
||||
|
||||
6. **SSE Events** ([event_types.py](backend/events/types/event_types.py), [tracking_event_created.py](backend/events/types/tracking_event_created.py))
|
||||
- New event type: `TRACKING_EVENT_CREATED`
|
||||
- Payload: `tracking_event_id`, `child_id`, `entity_type`, `action`
|
||||
- Emitted on every tracking event creation
|
||||
|
||||
---
|
||||
|
||||
### Frontend
|
||||
|
||||
1. **TypeScript Models** ([models.ts](frontend/vue-app/src/common/models.ts))
|
||||
- `TrackingEvent` interface (1:1 parity with Python)
|
||||
- Type aliases: `EntityType`, `ActionType`
|
||||
- `TrackingEventCreatedPayload` for SSE events
|
||||
|
||||
2. **API Helpers** ([api.ts](frontend/vue-app/src/common/api.ts))
|
||||
- `getTrackingEventsForChild()` function with all filter params
|
||||
|
||||
3. **SSE Registration**
|
||||
- Event type registered in type union
|
||||
- Ready for future UI components
|
||||
|
||||
---
|
||||
|
||||
### Tests
|
||||
|
||||
**Backend Unit Tests** ([test_tracking.py](backend/tests/test_tracking.py)):
|
||||
|
||||
- ✅ Tracking event creation with factory method
|
||||
- ✅ Delta invariant validation
|
||||
- ✅ Insert and query tracking events
|
||||
- ✅ Filtering by `entity_type` and `action`
|
||||
- ✅ Offset-based pagination
|
||||
- ✅ User anonymization on deletion
|
||||
- ✅ Points change correctness (positive/negative/zero delta)
|
||||
- ✅ No points change for request/cancel actions
|
||||
|
||||
---
|
||||
|
||||
## 🔑 Key Design Decisions
|
||||
|
||||
1. **Append-only tracking table** - No deletions, only anonymization on user deletion
|
||||
2. **Server timestamps** - `occurred_at` always uses server time (UTC) to avoid client clock drift
|
||||
3. **Separate logging** - Per-user audit logs independent of database
|
||||
4. **Offset pagination** - Simpler than cursors, sufficient for expected scale
|
||||
5. **No UI (yet)** - API/models/SSE only; UI deferred to future phase
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Usage Examples
|
||||
|
||||
### Backend: Create a tracking event
|
||||
|
||||
```python
|
||||
from models.tracking_event import TrackingEvent
|
||||
from db.tracking import insert_tracking_event
|
||||
from utils.tracking_logger import log_tracking_event
|
||||
|
||||
event = TrackingEvent.create_event(
|
||||
user_id='user123',
|
||||
child_id='child456',
|
||||
entity_type='task',
|
||||
entity_id='task789',
|
||||
action='activated',
|
||||
points_before=50,
|
||||
points_after=60,
|
||||
metadata={'task_name': 'Homework'}
|
||||
)
|
||||
|
||||
insert_tracking_event(event)
|
||||
log_tracking_event(event)
|
||||
```
|
||||
|
||||
### Frontend: Query tracking events
|
||||
|
||||
```typescript
|
||||
import { getTrackingEventsForChild } from "@/common/api";
|
||||
|
||||
const res = await getTrackingEventsForChild({
|
||||
childId: "child456",
|
||||
entityType: "task",
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
// { tracking_events: [...], total: 42, count: 20, limit: 20, offset: 0 }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Migration Notes
|
||||
|
||||
1. **New database file**: `backend/data/db/tracking_events.json` will be created automatically on first tracking event.
|
||||
2. **New log directory**: `backend/logs/tracking_user_<user_id>.log` files will be created per user.
|
||||
3. **No breaking changes** to existing APIs or data models.
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Enhancements (Not in This Phase)
|
||||
|
||||
- Admin/parent UI for viewing tracking history
|
||||
- Badges and certificates based on tracking data
|
||||
- Analytics and reporting dashboards
|
||||
- Export tracking data (CSV, JSON)
|
||||
- Time-based filters (date range queries)
|
||||
BIN
.github/specs/active/feat-dynamic-points/feat-dynamic-points-after.png
vendored
Normal file
BIN
.github/specs/active/feat-dynamic-points/feat-dynamic-points-after.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
.github/specs/active/feat-dynamic-points/feat-dynamic-points-before.png
vendored
Normal file
BIN
.github/specs/active/feat-dynamic-points/feat-dynamic-points-before.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
112
.github/specs/active/feat-dynamic-points/feat-tracking.md
vendored
Normal file
112
.github/specs/active/feat-dynamic-points/feat-tracking.md
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
# Feature: Task and Reward Tracking
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:** Tasks, Penalties, and Rewards should be recorded when completed (activated), requested, redeemed, and cancelled. A record of the date and time should also be kept for these actions. A log file shall be produced that shows the child's points before and after the action happened.
|
||||
|
||||
**User Story:**
|
||||
As an administrator, I want to know what kind and when a task, penalty, or reward was activated.
|
||||
As an administrator, I want a log created detailing when a task, penalty, or reward was activated and how points for the affected child has changed.
|
||||
As a user (parent), when I activate a task or penalty, I want to record the time and what task or penalty was activated.
|
||||
As a user (parent), when I redeem a reward, I want to record the time and what reward was redeeemed.
|
||||
As a user (parent/child), when I cancel a reward, I want to record the time and what reward was cancelled.
|
||||
As a user (child), when I request a reward, I want to record the time and what reward was requested.
|
||||
|
||||
**Questions:**
|
||||
|
||||
- Tasks/Penalty, rewards should be tracked per child. Should the tracking be recorded in the child database, or should a new database be used linking the tracking to the child?
|
||||
- If using a new database, should tracking also be linking to user in case of account deletion?
|
||||
- Does there need to be any frontend changes for now?
|
||||
|
||||
**Decisions:**
|
||||
|
||||
- Use a **new TinyDB table** (`tracking_events.json`) for tracking records (append-only). Do **not** embed tracking in `child` to avoid large child docs and preserve audit history. Each record includes `child_id` and `user_id`.
|
||||
- Track events for: task/penalty activated, reward requested, reward redeemed, reward cancelled.
|
||||
- Store timestamps in **UTC ISO 8601** with timezone (e.g. `2026-02-09T18:42:15Z`). Always use **server time** for `occurred_at` to avoid client clock drift.
|
||||
- On user deletion: **anonymize** tracking records by setting `user_id` to `null`, preserving child activity history for compliance/audit.
|
||||
- Keep an **audit log file per user** (e.g. `tracking_user_<user_id>.log`) with points before/after and event metadata. Use rotating file handler.
|
||||
- Use **offset-based pagination** for tracking queries (simpler with TinyDB, sufficient for expected scale).
|
||||
- **Frontend changes deferred**: Ship backend API, models, and SSE events only. No UI components in this phase.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
## Acceptance Criteria (Definition of Done)
|
||||
|
||||
### Data Model
|
||||
|
||||
- [x] Add `TrackingEvent` model in `backend/models/` with `from_dict()`/`to_dict()` and 1:1 TS interface in [frontend/vue-app/src/common/models.ts](frontend/vue-app/src/common/models.ts)
|
||||
- [x] `TrackingEvent` fields include: `id`, `user_id`, `child_id`, `entity_type` (task|reward|penalty), `entity_id`, `action` (activated|requested|redeemed|cancelled), `points_before`, `points_after`, `delta`, `occurred_at`, `created_at`, `metadata` (optional dict)
|
||||
- [x] Ensure `delta == points_after - points_before` invariant
|
||||
|
||||
### Backend Implementation
|
||||
|
||||
- [x] Create TinyDB table (e.g., `tracking_events.json`) with helper functions in `backend/db/`
|
||||
- [x] Add tracking write in all mutation endpoints:
|
||||
- task/penalty activation
|
||||
- reward request
|
||||
- reward redeem
|
||||
- reward cancel
|
||||
- [x] Build `TrackingEvent` instances from models (no raw dict writes)
|
||||
- [x] Add server-side validation for required fields and action/entity enums
|
||||
- [x] Add `send_event_for_current_user` calls for tracking mutations
|
||||
|
||||
### Frontend Implementation
|
||||
|
||||
- [x] Add `TrackingEvent` interface and enums to [frontend/vue-app/src/common/models.ts](frontend/vue-app/src/common/models.ts)
|
||||
- [x] Add API helpers for tracking (list per child, optional filters) in [frontend/vue-app/src/common/api.ts](frontend/vue-app/src/common/api.ts)
|
||||
- [x] Register SSE event type `tracking_event_created` in [frontend/vue-app/src/common/backendEvents.ts](frontend/vue-app/src/common/backendEvents.ts)
|
||||
- [x] **No UI components** — deferred to future phase
|
||||
|
||||
### Admin API
|
||||
|
||||
- [x] Add admin endpoint to query tracking by `child_id`, date range, and `entity_type` (e.g. `GET /admin/tracking`)
|
||||
- [x] Add offset-based pagination parameters (`limit`, `offset`) with sensible defaults (e.g. limit=50, max=500)
|
||||
- [x] Return total count for pagination UI (future)
|
||||
|
||||
### SSE Event
|
||||
|
||||
- [x] Add event type `tracking_event_created` with payload containing `tracking_event_id` and minimal denormalized info
|
||||
- [x] Update [backend/events/types/event_types.py](backend/events/types/event_types.py) and [frontend/vue-app/src/common/backendEvents.ts](frontend/vue-app/src/common/backendEvents.ts)
|
||||
|
||||
### Backend Unit Tests
|
||||
|
||||
- [x] Create tests for tracking creation on each mutation endpoint (task/penalty activated, reward requested/redeemed/cancelled)
|
||||
- [x] Validate `points_before/after` and `delta` are correct
|
||||
- [x] Ensure tracking write does not block core mutation (failure behavior defined)
|
||||
|
||||
### Frontend Unit Tests
|
||||
|
||||
- [x] Test API helper functions for tracking queries
|
||||
- [x] Test TypeScript interface matches backend model (type safety)
|
||||
|
||||
#### Edge Cases
|
||||
|
||||
- [x] Reward cancel after redeem should not create duplicate inconsistent entries
|
||||
- [x] Multiple activations in rapid sequence must be ordered by `occurred_at` then `created_at`
|
||||
- [x] Child deleted: tracking records retained and still queryable by admin (archive mode)
|
||||
- [x] User deleted: anonymize tracking by setting `user_id` to `null`, retain all other fields for audit history
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
- [x] End-to-end: activate task -> tracking created -> SSE event emitted -> audit log written
|
||||
- [x] Verify user deletion anonymizes tracking records without breaking queries
|
||||
|
||||
### Logging & Monitoring
|
||||
|
||||
- [x] Add dedicated tracking logger with **per-user rotating file handler** (e.g. `logs/tracking_user_<user_id>.log`)
|
||||
- [x] Log one line per tracking event with `user_id`, `child_id`, `entity_type`, `entity_id`, `action`, `points_before`, `points_after`, `delta`, `occurred_at`
|
||||
- [x] Configure max file size and backup count (e.g. 10MB, 5 backups)
|
||||
|
||||
### Documentation
|
||||
|
||||
- [x] Update README or docs to include tracking endpoints, schema, and sample responses
|
||||
- [x] Add migration note for new `tracking_events.json`
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- Reward tracking will be used to determine child ranking (badges and certificates!)
|
||||
- is_good vs not is_good in task tracking can be used to show the child their balance in good vs not good
|
||||
318
.github/specs/archive/feat-account-delete-scheduler.md
vendored
Normal file
318
.github/specs/archive/feat-account-delete-scheduler.md
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
# Feature: Account Deletion Scheduler
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:** Implement a scheduler in the backend that will delete accounts that are marked for deletion after a period of time.
|
||||
|
||||
**User Story:**
|
||||
As an administrator, I want accounts that are marked for deletion to be deleted around X amount of hours after they were marked. I want the time to be adjustable.
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `ACCOUNT_DELETION_THRESHOLD_HOURS`: Hours to wait before deleting marked accounts (default: 720 hours / 30 days)
|
||||
- **Minimum:** 24 hours (enforced for safety)
|
||||
- **Maximum:** 720 hours (30 days)
|
||||
- Configurable via environment variable with validation on startup
|
||||
|
||||
### Scheduler Settings
|
||||
|
||||
- **Check Interval:** Every 1 hour
|
||||
- **Implementation:** APScheduler (BackgroundScheduler)
|
||||
- **Restart Handling:** On app restart, scheduler checks for users with `deletion_in_progress = True` and retries them
|
||||
- **Retry Logic:** Maximum 3 attempts per user; tracked via `deletion_attempted_at` timestamp
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### User Model (`backend/models/user.py`)
|
||||
|
||||
Add two new fields to the `User` dataclass:
|
||||
|
||||
- `deletion_in_progress: bool` - Default `False`. Set to `True` when deletion is actively running
|
||||
- `deletion_attempted_at: datetime | None` - Default `None`. Timestamp of last deletion attempt
|
||||
|
||||
**Serialization:**
|
||||
|
||||
- Both fields must be included in `to_dict()` and `from_dict()` methods
|
||||
|
||||
---
|
||||
|
||||
## Deletion Process & Order
|
||||
|
||||
When a user is due for deletion (current time >= `marked_for_deletion_at` + threshold), the scheduler performs deletion in this order:
|
||||
|
||||
1. **Set Flag:** `deletion_in_progress = True` (prevents concurrent deletion)
|
||||
2. **Pending Rewards:** Remove all pending rewards for user's children
|
||||
3. **Children:** Remove all children belonging to the user
|
||||
4. **Tasks:** Remove all user-created tasks (where `user_id` matches)
|
||||
5. **Rewards:** Remove all user-created rewards (where `user_id` matches)
|
||||
6. **Images (Database):** Remove user's uploaded images from `image_db`
|
||||
7. **Images (Filesystem):** Delete `data/images/[user_id]` directory and all contents
|
||||
8. **User Record:** Remove the user from `users_db`
|
||||
9. **Clear Flag:** `deletion_in_progress = False` (only if deletion failed; otherwise user is deleted)
|
||||
10. **Update Timestamp:** Set `deletion_attempted_at` to current time (if deletion failed)
|
||||
|
||||
### Error Handling
|
||||
|
||||
- If any step fails, log the error and continue to next step
|
||||
- If deletion fails completely, update `deletion_attempted_at` and set `deletion_in_progress = False`
|
||||
- If a user has 3 failed attempts, log a critical error but continue processing other users
|
||||
- Missing directories or empty tables are not considered errors
|
||||
|
||||
---
|
||||
|
||||
## Admin API Endpoints
|
||||
|
||||
### New Blueprint: `backend/api/admin_api.py`
|
||||
|
||||
All endpoints require JWT authentication and admin privileges.
|
||||
|
||||
**Note:** Endpoint paths below are as defined in Flask (without `/api` prefix). Frontend accesses them via nginx proxy at `/api/admin/*`.
|
||||
|
||||
#### `GET /admin/deletion-queue`
|
||||
|
||||
Returns list of users pending deletion.
|
||||
|
||||
**Response:** JSON with `count` and `users` array containing user objects with fields: `id`, `email`, `marked_for_deletion_at`, `deletion_due_at`, `deletion_in_progress`, `deletion_attempted_at`
|
||||
|
||||
#### `GET /admin/deletion-threshold`
|
||||
|
||||
Returns current deletion threshold configuration.
|
||||
|
||||
**Response:** JSON with `threshold_hours`, `threshold_min`, and `threshold_max` fields
|
||||
|
||||
#### `PUT /admin/deletion-threshold`
|
||||
|
||||
Updates deletion threshold (requires admin auth).
|
||||
|
||||
**Request:** JSON with `threshold_hours` field
|
||||
|
||||
**Response:** JSON with `message` and updated `threshold_hours`
|
||||
|
||||
**Validation:**
|
||||
|
||||
- Must be between 24 and 720 hours
|
||||
- Returns 400 error if out of range
|
||||
|
||||
#### `POST /admin/deletion-queue/trigger`
|
||||
|
||||
Manually triggers the deletion scheduler (processes entire queue immediately).
|
||||
|
||||
**Response:** JSON with `message`, `processed`, `deleted`, and `failed` counts
|
||||
|
||||
---
|
||||
|
||||
## SSE Event
|
||||
|
||||
### New Event Type: `USER_DELETED`
|
||||
|
||||
**File:** `backend/events/types/user_deleted.py`
|
||||
|
||||
**Payload fields:**
|
||||
|
||||
- `user_id: str` - ID of deleted user
|
||||
- `email: str` - Email of deleted user
|
||||
- `deleted_at: str` - ISO format timestamp of deletion
|
||||
|
||||
**Broadcasting:**
|
||||
|
||||
- Event is sent only to **admin users** (not broadcast to all users)
|
||||
- Triggered immediately after successful user deletion
|
||||
- Frontend admin clients can listen to this event to update UI
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### File Structure
|
||||
|
||||
- `backend/config/deletion_config.py` - Configuration with env variable
|
||||
- `backend/utils/account_deletion_scheduler.py` - Scheduler logic
|
||||
- `backend/api/admin_api.py` - New admin endpoints
|
||||
- `backend/events/types/user_deleted.py` - New SSE event
|
||||
|
||||
### Scheduler Startup
|
||||
|
||||
In `backend/main.py`, import and call `start_deletion_scheduler()` after Flask app setup
|
||||
|
||||
### Logging Strategy
|
||||
|
||||
**Configuration:**
|
||||
|
||||
- Use dedicated logger: `account_deletion_scheduler`
|
||||
- Log to both stdout (for Docker/dev) and rotating file (for persistence)
|
||||
- File: `logs/account_deletion.log`
|
||||
- Rotation: 10MB max file size, keep 5 backups
|
||||
- Format: `%(asctime)s - %(name)s - %(levelname)s - %(message)s`
|
||||
|
||||
**Log Levels:**
|
||||
|
||||
- **INFO:** Each deletion step (e.g., "Deleted 5 children for user {user_id}")
|
||||
- **INFO:** Summary after each run (e.g., "Deletion scheduler run: 3 users processed, 2 deleted, 1 failed")
|
||||
- **ERROR:** Individual step failures (e.g., "Failed to delete images for user {user_id}: {error}")
|
||||
- **CRITICAL:** User with 3+ failed attempts (e.g., "User {user_id} has failed deletion 3 times")
|
||||
- **WARNING:** Threshold set below 168 hours (7 days)
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria (Definition of Done)
|
||||
|
||||
### Data Model
|
||||
|
||||
- [x] Add `deletion_in_progress` field to User model
|
||||
- [x] Add `deletion_attempted_at` field to User model
|
||||
- [x] Update `to_dict()` and `from_dict()` methods for serialization
|
||||
- [x] Update TypeScript User interface in frontend
|
||||
|
||||
### Configuration
|
||||
|
||||
- [x] Create `backend/config/deletion_config.py` with `ACCOUNT_DELETION_THRESHOLD_HOURS`
|
||||
- [x] Add environment variable support with default (720 hours)
|
||||
- [x] Enforce minimum threshold of 24 hours
|
||||
- [x] Enforce maximum threshold of 720 hours
|
||||
- [x] Log warning if threshold is less than 168 hours
|
||||
|
||||
### Backend Implementation
|
||||
|
||||
- [x] Create `backend/utils/account_deletion_scheduler.py`
|
||||
- [x] Implement APScheduler with 1-hour check interval
|
||||
- [x] Implement deletion logic in correct order (pending_rewards → children → tasks → rewards → images → directory → user)
|
||||
- [x] Add comprehensive error handling (log and continue)
|
||||
- [x] Add restart handling (check `deletion_in_progress` flag on startup)
|
||||
- [x] Add retry logic (max 3 attempts per user)
|
||||
- [x] Integrate scheduler into `backend/main.py` startup
|
||||
|
||||
### Admin API
|
||||
|
||||
- [x] Create `backend/api/admin_api.py` blueprint
|
||||
- [x] Implement `GET /admin/deletion-queue` endpoint
|
||||
- [x] Implement `GET /admin/deletion-threshold` endpoint
|
||||
- [x] Implement `PUT /admin/deletion-threshold` endpoint
|
||||
- [x] Implement `POST /admin/deletion-queue/trigger` endpoint
|
||||
- [x] Add JWT authentication checks for all admin endpoints
|
||||
- [x] Add admin role validation
|
||||
|
||||
### SSE Event
|
||||
|
||||
- [x] Create `backend/events/types/user_deleted.py`
|
||||
- [x] Add `USER_DELETED` to `event_types.py`
|
||||
- [x] Implement admin-only event broadcasting
|
||||
- [x] Trigger event after successful deletion
|
||||
|
||||
### Backend Unit Tests
|
||||
|
||||
#### Configuration Tests
|
||||
|
||||
- [x] Test default threshold value (720 hours)
|
||||
- [x] Test environment variable override
|
||||
- [x] Test minimum threshold enforcement (24 hours)
|
||||
- [x] Test maximum threshold enforcement (720 hours)
|
||||
- [x] Test invalid threshold values (negative, non-numeric)
|
||||
|
||||
#### Scheduler Tests
|
||||
|
||||
- [x] Test scheduler identifies users ready for deletion (past threshold)
|
||||
- [x] Test scheduler ignores users not yet due for deletion
|
||||
- [x] Test scheduler handles empty database
|
||||
- [x] Test scheduler runs at correct interval (1 hour)
|
||||
- [x] Test scheduler handles restart with `deletion_in_progress = True`
|
||||
- [x] Test scheduler respects retry limit (max 3 attempts)
|
||||
|
||||
#### Deletion Process Tests
|
||||
|
||||
- [x] Test deletion removes pending_rewards for user's children
|
||||
- [x] Test deletion removes children for user
|
||||
- [x] Test deletion removes user's tasks (not system tasks)
|
||||
- [x] Test deletion removes user's rewards (not system rewards)
|
||||
- [x] Test deletion removes user's images from database
|
||||
- [x] Test deletion removes user directory from filesystem
|
||||
- [x] Test deletion removes user record from database
|
||||
- [x] Test deletion handles missing directory gracefully
|
||||
- [x] Test deletion order is correct (children before user, etc.)
|
||||
- [x] Test `deletion_in_progress` flag is set during deletion
|
||||
- [x] Test `deletion_attempted_at` is updated on failure
|
||||
|
||||
#### Edge Cases
|
||||
|
||||
- [x] Test deletion with user who has no children
|
||||
- [x] Test deletion with user who has no custom tasks/rewards
|
||||
- [x] Test deletion with user who has no uploaded images
|
||||
- [x] Test partial deletion failure (continue with other users)
|
||||
- [x] Test concurrent deletion attempts (flag prevents double-deletion)
|
||||
- [x] Test user with exactly 3 failed attempts (logs critical, no retry)
|
||||
|
||||
#### Admin API Tests
|
||||
|
||||
- [x] Test `GET /admin/deletion-queue` returns correct users
|
||||
- [x] Test `GET /admin/deletion-queue` requires authentication
|
||||
- [x] Test `GET /admin/deletion-threshold` returns current threshold
|
||||
- [x] Test `PUT /admin/deletion-threshold` updates threshold
|
||||
- [x] Test `PUT /admin/deletion-threshold` validates min/max
|
||||
- [x] Test `PUT /admin/deletion-threshold` requires admin role
|
||||
- [x] Test `POST /admin/deletion-queue/trigger` triggers scheduler
|
||||
- [x] Test `POST /admin/deletion-queue/trigger` returns summary
|
||||
|
||||
#### Integration Tests
|
||||
|
||||
- [x] Test full deletion flow from marking to deletion
|
||||
- [x] Test multiple users deleted in same scheduler run
|
||||
- [x] Test deletion with restart midway (recovery)
|
||||
|
||||
### Logging & Monitoring
|
||||
|
||||
- [x] Configure dedicated scheduler logger with rotating file handler
|
||||
- [x] Create `logs/` directory for log files
|
||||
- [x] Log each deletion step with INFO level
|
||||
- [x] Log summary after each scheduler run (users processed, deleted, failed)
|
||||
- [x] Log errors with user ID for debugging
|
||||
- [x] Log critical error for users with 3+ failed attempts
|
||||
- [x] Log warning if threshold is set below 168 hours
|
||||
|
||||
### Documentation
|
||||
|
||||
- [x] Create `README.md` at project root
|
||||
- [x] Document scheduler feature and behavior
|
||||
- [x] Document environment variable `ACCOUNT_DELETION_THRESHOLD_HOURS`
|
||||
- [x] Document deletion process and order
|
||||
- [x] Document admin API endpoints
|
||||
- [x] Document restart/retry behavior
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
All tests should use `DB_ENV=test` and operate on test databases in `backend/test_data/`.
|
||||
|
||||
### Unit Test Files
|
||||
|
||||
- `backend/tests/test_deletion_config.py` - Configuration validation
|
||||
- `backend/tests/test_deletion_scheduler.py` - Scheduler logic
|
||||
- `backend/tests/test_admin_api.py` - Admin endpoints
|
||||
|
||||
### Test Fixtures
|
||||
|
||||
- Create users with various `marked_for_deletion_at` timestamps
|
||||
- Create users with children, tasks, rewards, images
|
||||
- Create users with `deletion_in_progress = True` (for restart tests)
|
||||
|
||||
### Assertions
|
||||
|
||||
- Database records are removed in correct order
|
||||
- Filesystem directories are deleted
|
||||
- Flags and timestamps are updated correctly
|
||||
- Error handling works (log and continue)
|
||||
- Admin API responses match expected format
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- Archive deleted accounts instead of hard deletion
|
||||
- Email notification to admin when deletion completes
|
||||
- Configurable retry count (currently hardcoded to 3)
|
||||
- Soft delete with recovery option (within grace period)
|
||||
Reference in New Issue
Block a user