chore: relocate backend into apps/api ahead of monorepo merge

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 12:20:40 +08:00
co-authored by Claude Sonnet 5
parent af44598df6
commit eaa6d2276a
388 changed files with 0 additions and 13998 deletions
@@ -0,0 +1,129 @@
# User Activity Controller Documentation
**File:** `controllers/admin/user_activity.controller.js`
**Base URL:** `/api/admin/activity` and `/api/admin/users/:id/activity`
**Guards:** `authenticate → requireAdmin() → adminLimiter`
---
## Table of Contents
- [Get Global Activity Feed](#get-global-activity-feed)
- [Get Per-User Activity](#get-per-user-activity)
---
## Notes
- `user_activity` rows are **never soft-deleted** — this is an append-only audit log.
- `created_at` is the timestamp column (no `createdAt` alias — Sequelize `timestamps: false`).
- The global feed endpoint joins the `users` table and enriches each row with `full_name` and `avatar_url`.
- The per-user endpoint skips the join for performance since user context is already known.
---
## Activity Object Shape
```json
{
"activity_id": 1,
"user_id": 42,
"email": "user@example.com",
"full_name": "Kenneth Obsequio",
"avatar_url": "https://...",
"acc_type": "admin",
"action": "login",
"entity_type": "session",
"entity_id": 7,
"details": { "session_id": 7, "reg_type": "system" },
"created_at": "2026-06-21T08:00:00.000Z"
}
```
### Common `action` Values
| Action | Entity Type | Details Keys |
|--------|-------------|--------------|
| `login` | `session` | `session_id`, `reg_type` |
| `deactivate_user` | `user` | `target_email` |
| `lesson_read` | `lesson` | `lesson_uuid`, `status` |
| `submit_task` | `task` | `task_id` |
| `set_user_status` | `user` | `is_active` |
| `create_course` | `course` | `title` |
| `update_course` | `course` | `title` |
| `archive_course` | `course` | — |
| `create_task_list` | `task_list` | `name` |
| `create_advertisement` | `advertisement` | `type` |
| `grant_tier` | `tier` | `user_id`, `tier`, `plan_id` |
| `revoke_tier` | `tier` | `user_id`, `tier` |
---
## Get Global Activity Feed
**`GET /api/admin/activity`**
Returns a paginated, reverse-chronological feed of all user activity across the system.
### Query Parameters
| Parameter | Type | Required | Description |
|-----------|--------|----------|-------------|
| `page` | number | No | Default: `1` |
| `limit` | number | No | Default: `20`, max: `100` |
| `action` | string | No | Filter by exact action string (e.g. `login`) |
| `from` | date | No | ISO date — `created_at >= from` |
| `to` | date | No | ISO date — `created_at <= to` |
### Response `200`
```json
{
"status": "success",
"message": "Activity feed retrieved.",
"data": {
"total": 500,
"page": 1,
"totalPages": 25,
"activities": [ ... ]
}
}
```
---
## Get Per-User Activity
**`GET /api/admin/users/:id/activity`**
Returns a paginated, reverse-chronological activity log for a single user.
### Query Parameters
Same as global feed (`page`, `limit`, `action`, `from`, `to`).
### Response `200`
```json
{
"status": "success",
"message": "User activity retrieved.",
"data": {
"total": 45,
"page": 1,
"totalPages": 3,
"activities": [
{
"activity_id": 1,
"user_id": 42,
"session_id": null,
"action": "update_course",
"entity_type": "course",
"entity_id": 3,
"details": { "title": "Advanced JS" },
"created_at": "2026-06-21T09:00:00.000Z"
}
]
}
}
```
### Error Responses
| Status | Message |
|--------|---------|
| `400` | `Invalid User ID.` |
| `404` | `User not found.` |