Files
starr-philproperties/controllers/admin/documentation/assets.md
T
2026-05-12 00:09:09 +08:00

11 KiB

Assets API

Base path: /api/admin/assets Controller: controllers/admin/assets.controller.js Storage: Chibisafe (CDN) + PostgreSQL via Sequelize


Prerequisites

Multer setup

The upload and update-thumbnail endpoints use multer.fields() — make sure your route file is configured with memoryStorage:

const multer = require("multer");
const upload = multer({ storage: multer.memoryStorage() });

// Upload
router.post("/",               upload.fields([{ name: "file", maxCount: 1 }, { name: "thumbnail", maxCount: 1 }]), assetsCtrl.uploadAsset);

// Update thumbnail
router.patch("/:assetId/thumbnail", upload.fields([{ name: "thumbnail", maxCount: 1 }]), assetsCtrl.updateThumbnail);

Environment variables

CHIBISAFE_BASE_URL=https://cdn.yourdomain.com
CHIBISAFE_API_KEY=your-api-key

CHIBISAFE_ALBUM_AVATARS=uuid
CHIBISAFE_ALBUM_VIDEOS=uuid
CHIBISAFE_ALBUM_DOCUMENTS=uuid
CHIBISAFE_ALBUM_THUMBNAILS=uuid
CHIBISAFE_ALBUM_ARCHIVED=uuid

Album routing

owner_type is the single source of truth for which Chibisafe album a file lands in:

owner_type Chibisafe album Intended use
avatar avatars Profile pictures
video videos Course / content videos
document documents PDF, DOCX, PPT, TXT, etc.
thumbnail thumbnails Set automatically — do not send manually
image (none) General-purpose images
anything else (none) Unclassified

Endpoints


GET /

List all assets (paginated).

Query params

Param Required Description
page optional Page number. Default: 1
limit optional Items per page. Default: 10, max: 1000
filters optional JSON array of filter objects passed to buildQuery
sort optional JSON array of sort objects passed to buildQuery

Response 200

{
  "message": "Assets retrieved.",
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 10,
    "totalRecords": 42,
    "totalPages": 5,
    "hasPrevPage": false,
    "hasNextPage": true
  },
  "attributes": [...]
}

Soft-deleted assets are excluded automatically. Hidden fields (per adminExclude): checksum, storage_bucket, storage_key, deletedBy.


GET /:assetId

Get a single asset by primary key.

URL params

Param Required Description
assetId required Asset primary key (BIGINT)

Responses

Status Description
200 { data: asset }
400 Invalid asset ID
404 Asset not found
500 Internal server error

POST /

Upload a new asset.

Content-Type: multipart/form-data

File fields

Field Required Description
file required The main asset (image, video, document, etc.)
thumbnail required if video Cover image for the video. Ignored for non-video files.

Text fields

Field Required Default Description
uploadedBy required — User ID (BIGINT) of the uploader
storage_provider required — chibisafe | local | s3 | gcs | cloudinary
owner_type optional null Determines album routing: avatar, video, document, image
owner_id optional null ID of the owning entity (course ID, user ID, etc.)
display_name optional original filename Human-readable name shown in the UI
description optional null Free-text description
is_public optional false true | false
access_level optional private public | private | restricted
storage_bucket optional null Bucket name (S3 / GCS only)
storage_key optional null Override storage key. Auto-set for Chibisafe (uses Chibisafe file UUID).
file_url conditional — Required when storage_provider is not local or chibisafe
width optional (non-video) null Image/document width in px. Ignored for videos.
height optional (non-video) null Image/document height in px. Ignored for videos.

Auto-extracted fields (videos only — do not send)

These are extracted server-side via ffprobe and will override anything the client sends:

Field Source Example
width ffprobe 1920
height ffprobe 1080
resolution derived 1080p, 720p, 4K
duration ffprobe 281.49 (seconds)
frame_rate ffprobe 23.976 (fps)
bitrate ffprobe 447933 (bps)
video_codec ffprobe H.264, H.265, AV1, VP9
audio_codec ffprobe AAC, MP3, Opus
thumbnail_url Chibisafe upload CDN URL of the uploaded thumbnail

Transaction strategy

Phase 1 (no DB connection held — slow I/O):
  ├─ Validate inputs
  ├─ Upload main file to Chibisafe   → track UUID for rollback
  ├─ Run ffprobe on video buffer     → extract metadata
  └─ Upload thumbnail to Chibisafe  → track UUID for rollback

Phase 2 (transaction open ~milliseconds):
  └─ Asset.create()  →  commit

On Phase 2 failure:
  └─ rollback DB  +  deleteFile() all tracked Chibisafe UUIDs

Responses

Status Description
201 { data: asset } — fully populated asset record
400 Missing file, uploadedBy, or thumbnail (for videos); buffer issues
500 DB or Chibisafe error — Chibisafe uploads are cleaned up automatically

Example — video upload (Postman)

POST /api/admin/assets
Content-Type: multipart/form-data

file           → (attach .mp4)
thumbnail      → (attach .jpg)
uploadedBy     → 1
storage_provider → chibisafe
owner_type     → video
owner_id       → 10
display_name   → Intro to React
is_public      → true
access_level   → public

Example — avatar upload

POST /api/admin/assets
Content-Type: multipart/form-data

file             → (attach .jpg)
uploadedBy       → 1
storage_provider → chibisafe
owner_type       → avatar
owner_id         → 5

Example — document upload

POST /api/admin/assets
Content-Type: multipart/form-data

file             → (attach .pdf)
uploadedBy       → 1
storage_provider → chibisafe
owner_type       → document
owner_id         → 7
display_name     → Module 1 Handout

PATCH /:assetId/thumbnail

Replace the thumbnail image of an existing asset by uploading a new file.

Content-Type: multipart/form-data

URL params

Param Required Description
assetId required Asset primary key

File field

Field Required Description
thumbnail required New thumbnail image file

How it works

  1. Uploads the new thumbnail to Chibisafe (thumbnails album).
  2. Updates thumbnail_url on the asset record.
  3. Deletes the old thumbnail from Chibisafe (best-effort — non-fatal if it fails).

Note: Old thumbnail cleanup requires a thumbnail_storage_key column on the Asset model to track the previous Chibisafe file UUID. Without it, the old thumbnail remains on Chibisafe but the DB record is updated correctly.

Responses

Status Description
200 { data: asset } — updated asset with new thumbnail_url
400 No thumbnail file attached
404 Asset not found
500 Internal server error

PUT /:assetId

Update asset metadata. File uploads are blocked on this endpoint.

Content-Type: application/json

URL params

Param Required Description
assetId required Asset primary key

Body — all fields optional, send only what changes

Field Type Description
display_name string New display name
description string New description
owner_type string New owner type
owner_id number New owner entity ID
is_public boolean true | false
access_level string public | private | restricted
thumbnail_url string Manually replace thumbnail URL (use PATCH /thumbnail to upload a file instead)
width number Width in px. Re-derives resolution automatically.
height number Height in px. Re-derives resolution automatically.
duration number Duration in seconds
frame_rate number fps
bitrate number bps
video_codec string e.g. H.264
audio_codec string e.g. AAC

Responses

Status Description
200 { data: asset }
400 Invalid ID or file attached to request
404 Asset not found
500 Internal server error

DELETE /:assetId

Soft-delete a single asset.

Sets deletedAt on the DB record and moves the file to the archived album on Chibisafe (best-effort — non-fatal if Chibisafe is unavailable).

Content-Type: application/json

URL params

Param Required Description
assetId required Asset primary key

Body

Field Required Description
deletedBy optional User ID performing the delete

Responses

Status Description
200 Asset deleted
400 Invalid asset ID
404 Asset not found
500 Internal server error

DELETE /bulk

Soft-delete multiple assets in one call.

All matching Chibisafe files are moved to the archived album in a single API call.

Content-Type: application/json

Body

Field Required Description
ids required Non-empty array of asset IDs: [1, 2, 3]
deletedBy optional User ID performing the delete

Responses

Status Description
200 N asset(s) deleted
400 ids missing or empty
500 Internal server error

POST /:assetId/restore

Restore a soft-deleted asset.

Clears deletedAt and deletedBy on the DB record, then moves the file on Chibisafe from the archived album back to its home album based on owner_type:

owner_type Moved back to
video videos album
avatar avatars album
document documents album
image / anything else no move (no dedicated album)

The Chibisafe move is best-effort — a failed move will not block or roll back the DB restore.

URL params

Param Required Description
assetId required Asset primary key (must be soft-deleted)

Body: none required.

Responses

Status Description
200 { data: asset } — Asset restored
404 Asset not found or not deleted
500 Internal server error

Response shape

All responses use R.success / R.error from response.util:

// success
{
  "message": "Asset uploaded.",
  "data": { ... }
}

// error
{
  "message": "Asset not found.",
  "status": 404
}

File Purpose
models/assets/assets.mdl.js Sequelize model
models/assets/assets.attributes.js Exclude sets, paginate config
services/chibisafe.service.js Chibisafe API wrapper (upload, delete, archive, album)
services/ffprobe.service.js ffprobe metadata extraction for videos
utils/paginate.util.js Paginated findAndCountAll used by getAssets
utils/response.util.js R.success / R.error response helpers