mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Adjusted
This commit is contained in:
@@ -0,0 +1,380 @@
|
||||
# Assets Controller Documentation
|
||||
|
||||
**File:** `controllers/admin/assets.controller.js`
|
||||
**Base URL:** `/api/admin/assets`
|
||||
**Guards:** `authenticate → requireAdmin() → adminLimiter`
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Get All Assets](#get-all-assets)
|
||||
- [Get Single Asset](#get-single-asset)
|
||||
- [Upload Asset](#upload-asset)
|
||||
- [Update Asset Metadata](#update-asset-metadata)
|
||||
- [Update Thumbnail](#update-thumbnail)
|
||||
- [Delete Asset](#delete-asset)
|
||||
- [Bulk Delete Assets](#bulk-delete-assets)
|
||||
- [Restore Asset](#restore-asset)
|
||||
|
||||
---
|
||||
|
||||
## Get All Assets
|
||||
|
||||
**`GET /api/admin/assets`**
|
||||
|
||||
Returns a paginated list of non-deleted assets with optional filtering.
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-------------|---------|----------|--------------------------------------------------|
|
||||
| page | number | No | Page number. Default: `1` |
|
||||
| limit | number | No | Records per page. Default: `20` |
|
||||
| file_type | string | No | Filter by type: `image`, `video`, `document`, `other` |
|
||||
| owner_type | string | No | Filter by owner type e.g. `User`, `Course` |
|
||||
| owner_id | number | No | Filter by owner ID |
|
||||
| uploadedBy | number | No | Filter by uploader user ID |
|
||||
| is_public | boolean | No | Filter by visibility: `true` or `false` |
|
||||
| resolution | string | No | Filter by resolution e.g. `1080p`, `720p` |
|
||||
| search | string | No | Search by `display_name`, `original_name`, `description` |
|
||||
| sort_by | string | No | Column to sort by. Default: `createdAt` |
|
||||
| sort_dir | string | No | Sort direction: `ASC` or `DESC`. Default: `DESC` |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Assets retrieved.",
|
||||
"data": {
|
||||
"rows": [...],
|
||||
"pagination": {
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Single Asset
|
||||
|
||||
**`GET /api/admin/assets/:assetId`**
|
||||
|
||||
Returns a single non-deleted asset by ID.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------|
|
||||
| assetId | number | Yes | Asset ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Asset found.",
|
||||
"data": {
|
||||
"asset_id": 1,
|
||||
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"original_name": "intro.mp4",
|
||||
"display_name": "Course Intro Video",
|
||||
"file_url": "/uploads/intro.mp4",
|
||||
"file_size": 104857600,
|
||||
"mime_type": "video/mp4",
|
||||
"extension": "mp4",
|
||||
"checksum": "a3f5...",
|
||||
"file_type": "video",
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"duration": 120.5,
|
||||
"resolution": "1080p",
|
||||
"frame_rate": 29.97,
|
||||
"bitrate": 8000000,
|
||||
"video_codec": "H.264",
|
||||
"audio_codec": "AAC",
|
||||
"thumbnail_url": "/uploads/thumbnails/intro.jpg",
|
||||
"description": "Introduction to the course.",
|
||||
"storage_provider": "local",
|
||||
"storage_bucket": null,
|
||||
"storage_key": "intro.mp4",
|
||||
"is_public": true,
|
||||
"access_level": "public",
|
||||
"owner_type": "Course",
|
||||
"owner_id": 3,
|
||||
"uploadedBy": 1,
|
||||
"deletedBy": null,
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z",
|
||||
"deletedAt": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Asset not found."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Upload Asset
|
||||
|
||||
**`POST /api/admin/assets/upload`**
|
||||
|
||||
Uploads a new asset. Expects `multipart/form-data`.
|
||||
Video metadata (`width`, `height`, `duration`, etc.) should be extracted via **ffprobe** server-side or passed from the client.
|
||||
`resolution` is **auto-derived** from `width` and `height` — do not pass it manually.
|
||||
|
||||
### Request `multipart/form-data`
|
||||
| Field | Type | Required | Description |
|
||||
|-----------------|---------|----------|----------------------------------------------------------|
|
||||
| file | File | Yes | The file to upload |
|
||||
| uploadedBy | number | Yes | User ID of the uploader |
|
||||
| display_name | string | No | Display name shown on platform. Defaults to filename |
|
||||
| description | string | No | Description of the asset |
|
||||
| owner_type | string | No | Owning entity type e.g. `Course`, `User` |
|
||||
| owner_id | number | No | Owning entity ID |
|
||||
| is_public | boolean | No | Whether asset is publicly accessible. Default: `false` |
|
||||
| access_level | string | No | `public`, `private`, `restricted`. Default: `private` |
|
||||
| storage_provider| string | No | `local`, `s3`, `gcs`, `cloudinary`, `chibisafe`, `other`. Default: `local` |
|
||||
| storage_bucket | string | No | Bucket/container name for cloud storage |
|
||||
| storage_key | string | No | Object key/path in bucket |
|
||||
| file_url | string | No* | Required for non-local storage providers |
|
||||
| width | number | No | Video/image width in px |
|
||||
| height | number | No | Video/image height in px |
|
||||
| duration | number | No | Video duration in seconds |
|
||||
| frame_rate | number | No | Video frame rate in fps |
|
||||
| bitrate | number | No | Video bitrate in bps |
|
||||
| video_codec | string | No | Video codec e.g. `H.264`, `H.265` |
|
||||
| audio_codec | string | No | Audio codec e.g. `AAC`, `MP3` |
|
||||
| thumbnail_url | string | No | URL of the video/document preview thumbnail |
|
||||
|
||||
### Resolution Auto-Derivation
|
||||
| Height (px) | Derived Resolution |
|
||||
|-------------|-------------------|
|
||||
| ≥ 2160 | `4K` |
|
||||
| ≥ 1440 | `1440p` |
|
||||
| ≥ 1080 | `1080p` |
|
||||
| ≥ 720 | `720p` |
|
||||
| ≥ 480 | `480p` |
|
||||
| ≥ 360 | `360p` |
|
||||
| ≥ 240 | `240p` |
|
||||
| Other | `{width}x{height}`|
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Asset uploaded.",
|
||||
"data": { ...asset }
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "No file uploaded."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Asset Metadata
|
||||
|
||||
**`PUT /api/admin/assets/:assetId`**
|
||||
|
||||
Updates metadata of an existing asset. File replacement is not supported — upload a new asset instead.
|
||||
`resolution` is **auto-re-derived** if `width` or `height` is updated.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| assetId | number | Yes | Asset ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|--------------|---------|----------|------------------------------------------|
|
||||
| display_name | string | No | Updated display name |
|
||||
| description | string | No | Updated description |
|
||||
| owner_type | string | No | Updated owner type |
|
||||
| owner_id | number | No | Updated owner ID |
|
||||
| is_public | boolean | No | Updated visibility |
|
||||
| access_level | string | No | Updated access level |
|
||||
| thumbnail_url | string | No | Updated thumbnail URL |
|
||||
| width | number | No | Updated width — re-derives resolution |
|
||||
| height | number | No | Updated height — re-derives resolution |
|
||||
| duration | number | No | Updated duration |
|
||||
| frame_rate | number | No | Updated frame rate |
|
||||
| bitrate | number | No | Updated bitrate |
|
||||
| video_codec | string | No | Updated video codec |
|
||||
| audio_codec | string | No | Updated audio codec |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Asset updated.",
|
||||
"data": { ...asset }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Thumbnail
|
||||
|
||||
**`PATCH /api/admin/assets/:assetId/thumbnail`**
|
||||
|
||||
Updates only the thumbnail of an asset. Useful for video platforms where users frequently change the video cover independently.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| assetId | number | Yes | Asset ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|--------------|--------|----------|-------------------------|
|
||||
| thumbnail_url | string | Yes | New thumbnail URL |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Thumbnail updated.",
|
||||
"data": { ...asset }
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "thumbnail_url is required."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Delete Asset
|
||||
|
||||
**`DELETE /api/admin/assets/:assetId`**
|
||||
|
||||
Soft deletes a single asset by setting `deletedAt` and `deletedBy`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| assetId | number | Yes | Asset ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|----------|--------|----------|--------------------------------|
|
||||
| deletedBy | number | No | User ID of who deleted the asset |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Asset deleted."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Delete Assets
|
||||
|
||||
**`DELETE /api/admin/assets/bulk`**
|
||||
|
||||
Soft deletes multiple assets at once.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|----------|----------|----------|----------------------------------|
|
||||
| ids | number[] | Yes | Array of asset IDs to delete |
|
||||
| deletedBy | number | No | User ID of who deleted the assets |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "3 asset(s) deleted."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "ids must be a non-empty array."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Restore Asset
|
||||
|
||||
**`PATCH /api/admin/assets/:assetId/restore`**
|
||||
|
||||
Restores a soft-deleted asset by clearing `deletedAt` and `deletedBy`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| assetId | number | Yes | Asset ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Asset restored.",
|
||||
"data": { ...asset }
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Asset not found or not deleted."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return the following on server error:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Internal server error."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Size Limits
|
||||
|
||||
| Type | Max Size |
|
||||
|----------|----------|
|
||||
| Images | 10 GB |
|
||||
| Videos | 10 GB |
|
||||
| Documents| 10 GB |
|
||||
|
||||
> Limit is applied at the multer middleware level. Adjust in `assets.routes.js` if needed.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **File replacement** is not supported. To replace a file, delete the old asset and upload a new one.
|
||||
- **Checksum** (SHA-256) is computed on upload for duplicate detection.
|
||||
- **Polymorphic ownership** via `owner_type` + `owner_id` allows any entity (`Course`, `User`, `Post`, etc.) to own assets without a direct foreign key.
|
||||
- **Resolution** is always auto-derived from `width` and `height` — never set manually.
|
||||
- **Soft delete** sets `deletedAt` timestamp. Assets are excluded from all queries unless explicitly queried with `paranoid: false`.
|
||||
@@ -0,0 +1,491 @@
|
||||
# User Groups Controller Documentation
|
||||
|
||||
**File:** `controllers/admin/user_groups.controller.js`
|
||||
**Base URL:** `/api/admin/groups`
|
||||
**Guards:** `authenticate → requireAdmin() → adminLimiter`
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Get All Groups](#get-all-groups)
|
||||
- [Get Single Group](#get-single-group)
|
||||
- [Create Group](#create-group)
|
||||
- [Update Group](#update-group)
|
||||
- [Deactivate Group](#deactivate-group)
|
||||
- [Bulk Deactivate Groups](#bulk-deactivate-groups)
|
||||
- [Restore Group](#restore-group)
|
||||
- [Bulk Restore Groups](#bulk-restore-groups)
|
||||
- [Get Archived Groups](#get-archived-groups)
|
||||
- [Get Group Field Values](#get-group-field-values)
|
||||
- [Get Users In Group](#get-users-in-group)
|
||||
- [Get Users Not In Group](#get-users-not-in-group)
|
||||
- [Add Users To Group](#add-users-to-group)
|
||||
- [Remove Users From Group](#remove-users-from-group)
|
||||
|
||||
---
|
||||
|
||||
## Get All Groups
|
||||
|
||||
**`GET /api/admin/groups`**
|
||||
|
||||
Returns a paginated list of active groups.
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|----------|--------|----------|--------------------------------------------------|
|
||||
| page | number | No | Page number. Default: `1` |
|
||||
| limit | number | No | Records per page. Default: `20` |
|
||||
| search | string | No | Search across group fields |
|
||||
| sort_by | string | No | Column to sort by. Default: `createdAt` |
|
||||
| sort_dir | string | No | Sort direction: `ASC` or `DESC`. Default: `DESC` |
|
||||
| filters | array | No | Column filters from DataTable |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Groups retrieved.",
|
||||
"data": {
|
||||
"rows": [
|
||||
{
|
||||
"group_id": 1,
|
||||
"name": "Administrators",
|
||||
"description": "Full access group.",
|
||||
"is_active": true,
|
||||
"member_count": 5,
|
||||
"createdBy": 1,
|
||||
"updatedBy": null,
|
||||
"deletedBy": null,
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z",
|
||||
"deletedAt": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 10,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"totalPages": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Single Group
|
||||
|
||||
**`GET /api/admin/groups/:gid`**
|
||||
|
||||
Returns a single group with a paginated list of its members.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Query Parameters
|
||||
Same pagination/filter params as [Get All Groups](#get-all-groups) — applied to the members list.
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group retrieved.",
|
||||
"data": {
|
||||
"group": {
|
||||
"group_id": 1,
|
||||
"name": "Administrators",
|
||||
"description": "Full access group.",
|
||||
"is_active": true,
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z"
|
||||
},
|
||||
"members": {
|
||||
"rows": [ ...users ],
|
||||
"pagination": { ... }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Group not found."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Create Group
|
||||
|
||||
**`POST /api/admin/groups`**
|
||||
|
||||
Creates a new user group.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|------------|--------|----------|--------------------|
|
||||
| name | string | Yes | Group name |
|
||||
| description | string | No | Group description |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group created.",
|
||||
"data": {
|
||||
"group_id": 1,
|
||||
"name": "Administrators",
|
||||
"description": "Full access group.",
|
||||
"is_active": true,
|
||||
"createdBy": 1,
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Group name is required."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Group
|
||||
|
||||
**`PUT /api/admin/groups/:gid`**
|
||||
|
||||
Updates a group's name or description.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|------------|--------|----------|------------------------|
|
||||
| name | string | No | Updated group name |
|
||||
| description | string | No | Updated description |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group updated.",
|
||||
"data": { ...group }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deactivate Group
|
||||
|
||||
**`PATCH /api/admin/groups/:gid/deactivate`**
|
||||
|
||||
Soft deletes a group by setting `deletedAt` and `is_active: false`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group deactivated."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Group is already deactivated."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Deactivate Groups
|
||||
|
||||
**`DELETE /api/admin/groups/bulk`**
|
||||
|
||||
Soft deletes multiple groups at once.
|
||||
Already-deactivated groups are skipped and reported.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|-------|----------|----------|---------------------------|
|
||||
| ids | number[] | Yes | Array of group IDs |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "3 group(s) deactivated successfully.",
|
||||
"data": {
|
||||
"deactivated_ids": [1, 2, 3],
|
||||
"skipped_ids": [4]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Restore Group
|
||||
|
||||
**`PATCH /api/admin/groups/:gid/restore`**
|
||||
|
||||
Restores a soft-deleted group by clearing `deletedAt` and setting `is_active: true`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group restored."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Group is already active."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Restore Groups
|
||||
|
||||
**`POST /api/admin/groups/bulk/restore`**
|
||||
|
||||
Restores multiple soft-deleted groups at once.
|
||||
Already-active groups are skipped and reported.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|-------|----------|----------|---------------------------|
|
||||
| ids | number[] | Yes | Array of group IDs |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "3 group(s) restored successfully.",
|
||||
"data": {
|
||||
"restored_ids": [1, 2, 3],
|
||||
"skipped_ids": [4]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Archived Groups
|
||||
|
||||
**`GET /api/admin/groups/archived`**
|
||||
|
||||
Returns a paginated list of soft-deleted groups.
|
||||
|
||||
### Query Parameters
|
||||
Same as [Get All Groups](#get-all-groups).
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Archived groups retrieved.",
|
||||
"data": {
|
||||
"rows": [ ...soft-deleted groups ],
|
||||
"pagination": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Group Field Values
|
||||
|
||||
**`GET /api/admin/groups/field-values`**
|
||||
|
||||
Returns distinct values for a given column — used to populate filter dropdowns in the DataTable.
|
||||
Supports regular columns, date fields, and audit fields.
|
||||
JSONB fields are not supported for groups.
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|----------|--------|----------|--------------------|
|
||||
| field | string | Yes | Column name |
|
||||
|
||||
### Supported Field Types
|
||||
| Type | Example | Returns |
|
||||
|----------|-------------|--------------------------------|
|
||||
| Regular | `is_active` | Distinct values |
|
||||
| Date | `createdAt` | Distinct dates (no time) |
|
||||
| Audit by | `createdBy` | Full names of referenced users |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Field values retrieved.",
|
||||
"data": ["true", "false"]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Users In Group
|
||||
|
||||
**`GET /api/admin/groups/:gid/users`**
|
||||
|
||||
Returns all current members of a group with their `user_id` and `full_name`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Group members fetched.",
|
||||
"data": [
|
||||
{ "user_id": 1, "full_name": "John Doe" },
|
||||
{ "user_id": 2, "full_name": "Jane Smith" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Users Not In Group
|
||||
|
||||
**`GET /api/admin/groups/:gid/users/add`**
|
||||
|
||||
Returns all users who are **not** currently members of the group.
|
||||
Used to populate the Add Members sheet.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Users fetched.",
|
||||
"data": [
|
||||
{ "user_id": 3, "full_name": "Alice Johnson" },
|
||||
{ "user_id": 4, "full_name": "Bob Williams" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Add Users To Group
|
||||
|
||||
**`POST /api/admin/groups/:gid/users`**
|
||||
|
||||
Adds one or more users to a group.
|
||||
If a user was previously removed (soft-deleted membership), their membership is restored instead of duplicated.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|---------|----------|----------|------------------------------|
|
||||
| user_ids | number[] | Yes | Array of user IDs to add |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Users added to group."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Users not found: 5, 6"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Remove Users From Group
|
||||
|
||||
**`DELETE /api/admin/groups/:gid/users`**
|
||||
|
||||
Removes one or more users from a group via soft delete on the membership record.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| gid | number | Yes | Group ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|---------|----------|---------|---------------------------------|
|
||||
| user_ids | number[] | Yes | Array of user IDs to remove |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Users removed from group."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Memberships not found for users: 5, 6"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return the following on server error:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Internal server error."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Soft delete** — deactivation sets `deletedAt` + `is_active: false`. Groups are excluded from all queries unless explicitly queried with `paranoid: false`.
|
||||
- **Membership soft delete** — removing a user from a group soft-deletes the membership record. Re-adding the user restores the record rather than creating a duplicate.
|
||||
- **Audit fields** — `createdBy`, `updatedBy`, `deletedBy` store the `user_id` of the admin who performed the action.
|
||||
- **JSONB** — group fields do not support JSONB dot-notation filtering unlike users.
|
||||
@@ -0,0 +1,463 @@
|
||||
# Users Controller Documentation
|
||||
|
||||
**File:** `controllers/admin/users.controller.js`
|
||||
**Base URL:** `/api/admin/users`
|
||||
**Guards:** `authenticate → requireAdmin() → adminLimiter`
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
- [Get All Users](#get-all-users)
|
||||
- [Get Single User](#get-single-user)
|
||||
- [Add Staff User](#add-staff-user)
|
||||
- [Update User](#update-user)
|
||||
- [Deactivate User](#deactivate-user)
|
||||
- [Bulk Deactivate Users](#bulk-deactivate-users)
|
||||
- [Restore User](#restore-user)
|
||||
- [Bulk Restore Users](#bulk-restore-users)
|
||||
- [Get Archived Users](#get-archived-users)
|
||||
- [Get User Field Values](#get-user-field-values)
|
||||
- [Get User Sessions](#get-user-sessions)
|
||||
- [Terminate Session](#terminate-session)
|
||||
|
||||
---
|
||||
|
||||
## Get All Users
|
||||
|
||||
**`GET /api/admin/users`**
|
||||
|
||||
Returns a paginated list of active users.
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------------------------------------------|
|
||||
| page | number | No | Page number. Default: `1` |
|
||||
| limit | number | No | Records per page. Default: `20` |
|
||||
| search | string | No | Search across user fields |
|
||||
| sort_by | string | No | Column to sort by. Default: `createdAt` |
|
||||
| sort_dir | string | No | Sort direction: `ASC` or `DESC`. Default: `DESC` |
|
||||
| filters | array | No | Column filters from DataTable |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Users retrieved.",
|
||||
"data": {
|
||||
"rows": [
|
||||
{
|
||||
"user_id": 1,
|
||||
"email": "john@example.com",
|
||||
"acc_type": "admin",
|
||||
"reg_type": "system",
|
||||
"is_active": true,
|
||||
"is_verified": true,
|
||||
"personal_info": {
|
||||
"name": {
|
||||
"given_name": "John",
|
||||
"middle_name": null,
|
||||
"last_name": "Doe",
|
||||
"extension_name": null,
|
||||
"full_name": "John Doe"
|
||||
},
|
||||
"date_of_birth": null,
|
||||
"occupation": null,
|
||||
"addresses": [],
|
||||
"phone_number": []
|
||||
},
|
||||
"groups": [],
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z",
|
||||
"deletedAt": null
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"total": 100,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"totalPages": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Single User
|
||||
|
||||
**`GET /api/admin/users/:id`**
|
||||
|
||||
Returns a single user with their group memberships.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| id | number | Yes | User ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "User retrieved.",
|
||||
"data": {
|
||||
"user_id": 1,
|
||||
"email": "john@example.com",
|
||||
"acc_type": "admin",
|
||||
"is_active": true,
|
||||
"is_verified": true,
|
||||
"personal_info": { ... },
|
||||
"groups": [
|
||||
{ "group_id": 1, "name": "Administrators" }
|
||||
],
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"updatedAt": "2025-01-01T00:00:00.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "User not found."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Add Staff User
|
||||
|
||||
**`POST /api/admin/users/staff`**
|
||||
|
||||
Creates a new staff user with an auto-generated temporary password.
|
||||
A welcome email is sent with the credentials and a 24-hour expiry notice.
|
||||
The user is forced to change their password on first login.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|-----------------------------|--------|----------|--------------------------------------|
|
||||
| email | string | Yes | Staff user email address |
|
||||
| personal_info.name.given_name | string | Yes | First name |
|
||||
| personal_info.name.last_name | string | Yes | Last name |
|
||||
| personal_info.name.middle_name | string | No | Middle name |
|
||||
| personal_info.name.extension_name | string | No | Extension name e.g. `Jr.` |
|
||||
| personal_info.date_of_birth | string | No | Date of birth |
|
||||
| personal_info.occupation | string | No | Occupation |
|
||||
|
||||
### Response `201`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Staff user created successfully.",
|
||||
"data": {
|
||||
"user_id": 5,
|
||||
"email": "staff@example.com",
|
||||
"acc_type": "staff"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Response `409`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Email is already in use."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update User
|
||||
|
||||
**`PUT /api/admin/users/:id`**
|
||||
|
||||
Updates a user's account type, active status, or personal information.
|
||||
Admins cannot change their own `acc_type`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| id | number | Yes | User ID |
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|--------------|---------|----------|----------------------------------------------|
|
||||
| acc_type | string | No | `admin`, `staff`, `user` |
|
||||
| is_active | boolean | No | Active status |
|
||||
| personal_info | object | No | Personal information object |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "User updated.",
|
||||
"data": { ...user }
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Admins cannot change their own role."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Deactivate User
|
||||
|
||||
**`DELETE /api/admin/users/:id`**
|
||||
|
||||
Soft deletes a user by setting `deletedAt` and `is_active: false`.
|
||||
All active sessions are force-terminated.
|
||||
Admins cannot deactivate their own account.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| id | number | Yes | User ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "User deactivated successfully."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "You cannot deactivate your own account."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Deactivate Users
|
||||
|
||||
**`DELETE /api/admin/users/bulk`**
|
||||
|
||||
Soft deletes multiple users at once.
|
||||
Already-deactivated users are skipped and reported.
|
||||
All active sessions for deactivated users are force-terminated.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|-------|----------|----------|--------------------------|
|
||||
| ids | number[] | Yes | Array of user IDs |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "3 user(s) deactivated successfully.",
|
||||
"data": {
|
||||
"deactivated_ids": [1, 2, 3],
|
||||
"skipped_ids": [4]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Restore User
|
||||
|
||||
**`POST /api/admin/users/:id/restore`**
|
||||
|
||||
Restores a soft-deleted user by clearing `deletedAt` and setting `is_active: true`.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| id | number | Yes | User ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "User restored successfully."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "User is not deactivated."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Bulk Restore Users
|
||||
|
||||
**`POST /api/admin/users/bulk/restore`**
|
||||
|
||||
Restores multiple soft-deleted users at once.
|
||||
Already-active users are skipped and reported.
|
||||
|
||||
### Request Body `application/json`
|
||||
| Field | Type | Required | Description |
|
||||
|-------|----------|----------|--------------------------|
|
||||
| ids | number[] | Yes | Array of user IDs |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "3 user(s) restored successfully.",
|
||||
"data": {
|
||||
"restored_ids": [1, 2, 3],
|
||||
"skipped_ids": [4]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get Archived Users
|
||||
|
||||
**`GET /api/admin/users/archived`**
|
||||
|
||||
Returns a paginated list of soft-deleted users.
|
||||
|
||||
### Query Parameters
|
||||
Same as [Get All Users](#get-all-users).
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Archived users retrieved.",
|
||||
"data": {
|
||||
"rows": [ ...soft-deleted users ],
|
||||
"pagination": { ... }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get User Field Values
|
||||
|
||||
**`GET /api/admin/users/field-values`**
|
||||
|
||||
Returns distinct values for a given column — used to populate filter dropdowns in the DataTable.
|
||||
Supports regular columns, date fields, audit fields, and JSONB dot-notation.
|
||||
|
||||
### Query Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|----------|--------|----------|------------------------------------------------------|
|
||||
| field | string | Yes | Column name or JSONB path e.g. `personal_info.name.given_name` |
|
||||
|
||||
### Supported Field Types
|
||||
| Type | Example | Returns |
|
||||
|-------------|----------------------------------|--------------------------------|
|
||||
| Regular | `acc_type` | Distinct string values |
|
||||
| Date | `createdAt` | Distinct dates (no time) |
|
||||
| Audit by | `createdBy` | Full names of referenced users |
|
||||
| JSONB | `personal_info.name.given_name` | Distinct JSONB path values |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Field values retrieved.",
|
||||
"data": ["admin", "staff", "user"]
|
||||
}
|
||||
```
|
||||
|
||||
### Response `400`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Invalid or restricted field."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Get User Sessions
|
||||
|
||||
**`GET /api/admin/users/:id/sessions`**
|
||||
|
||||
Returns all sessions for a specific user, ordered by most recent.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|-------------|
|
||||
| id | number | Yes | User ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Sessions retrieved.",
|
||||
"data": [
|
||||
{
|
||||
"session_id": 1,
|
||||
"user_id": 1,
|
||||
"is_active": true,
|
||||
"ip_address": "192.168.1.1",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
"createdAt": "2025-01-01T00:00:00.000Z",
|
||||
"logout_info": null
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Terminate Session
|
||||
|
||||
**`DELETE /api/admin/users/:id/sessions/:sid`**
|
||||
|
||||
Force-terminates a specific user session.
|
||||
|
||||
### Path Parameters
|
||||
| Parameter | Type | Required | Description |
|
||||
|-----------|--------|----------|--------------|
|
||||
| id | number | Yes | User ID |
|
||||
| sid | number | Yes | Session ID |
|
||||
|
||||
### Response `200`
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Session terminated."
|
||||
}
|
||||
```
|
||||
|
||||
### Response `404`
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Session not found."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return the following on server error:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"message": "Internal server error."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Excluded fields** — `password`, `otp_code`, `otp_expires_at`, `must_change_password`, `password_expires_at` are never returned in any response.
|
||||
- **Soft delete** — deactivation sets `deletedAt` + `is_active: false`. Users are excluded from all queries unless explicitly queried with `paranoid: false`.
|
||||
- **Session termination** — deactivating a user (single or bulk) always force-terminates all their active sessions.
|
||||
- **Audit fields** — `createdBy`, `updatedBy`, `deletedBy` store the `user_id` of the admin who performed the action.
|
||||
Reference in New Issue
Block a user