feat: Implement task and reward tracking feature
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:
2026-02-09 15:39:43 -05:00
parent 27f02224ab
commit 3dee8b80a2
20 changed files with 1450 additions and 0 deletions

View File

@@ -15,3 +15,23 @@ export function isEmailValid(email: string): boolean {
export function isPasswordStrong(password: string): boolean {
return /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]{8,}$/.test(password)
}
/**
* Fetch tracking events for a child with optional filters.
*/
export async function getTrackingEventsForChild(params: {
childId: string
entityType?: 'task' | 'reward' | 'penalty'
action?: 'activated' | 'requested' | 'redeemed' | 'cancelled'
limit?: number
offset?: number
}): Promise<Response> {
const query = new URLSearchParams()
query.set('child_id', params.childId)
if (params.entityType) query.set('entity_type', params.entityType)
if (params.action) query.set('action', params.action)
if (params.limit) query.set('limit', params.limit.toString())
if (params.offset) query.set('offset', params.offset.toString())
return fetch(`/api/admin/tracking?${query.toString()}`)
}

View File

@@ -93,6 +93,7 @@ export interface Event {
| ChildRewardsSetEventPayload
| TaskModifiedEventPayload
| RewardModifiedEventPayload
| TrackingEventCreatedPayload
}
export interface ChildModifiedEventPayload {
@@ -135,3 +136,45 @@ export interface RewardModifiedEventPayload {
reward_id: string
operation: 'ADD' | 'DELETE' | 'EDIT'
}
export interface TrackingEventCreatedPayload {
tracking_event_id: string
child_id: string
entity_type: EntityType
action: ActionType
}
export type EntityType = 'task' | 'reward' | 'penalty'
export type ActionType = 'activated' | 'requested' | 'redeemed' | 'cancelled'
export interface TrackingEvent {
id: string
user_id: string | null
child_id: string
entity_type: EntityType
entity_id: string
action: ActionType
points_before: number
points_after: number
delta: number
occurred_at: string
created_at: number
updated_at: number
metadata?: Record<string, any> | null
}
export const TRACKING_EVENT_FIELDS = [
'id',
'user_id',
'child_id',
'entity_type',
'entity_id',
'action',
'points_before',
'points_after',
'delta',
'occurred_at',
'created_at',
'updated_at',
'metadata',
] as const