84 lines
4.5 KiB
Markdown
84 lines
4.5 KiB
Markdown
---
|
|
type: api
|
|
title: Uploads Endpoints
|
|
description: Admin-only multipart upload endpoints for site logos, category icons, and challenge files.
|
|
tags: [api, uploads, multipart, admin]
|
|
timestamp: 2026-07-22T19:18:00Z
|
|
---
|
|
|
|
# Endpoints
|
|
|
|
| Method | Path | Auth | Source |
|
|
|--------|---------------------------------------|-------|---------------------------------------------------------|
|
|
| `POST` | `/api/v1/uploads/logo` | Admin | `backend/src/modules/uploads/uploads.controller.ts` |
|
|
| `POST` | `/api/v1/uploads/category-icon` | Admin | Same. |
|
|
| `POST` | `/api/v1/uploads/challenge-file` | Admin | Same. *(Removed in Job 861 — challenge attachments now flow through `POST /api/v1/admin/challenges/files/stage` so they are only persisted on challenge save.)* |
|
|
|
|
# Guard chain
|
|
|
|
All handlers are gated by the controller-level `AdminGuard` and `admin` role.
|
|
The global authentication and CSRF protections also apply; clients must send a
|
|
valid admin session and the standard CSRF cookie/header pair.
|
|
|
|
# Behavior
|
|
|
|
* Each endpoint uses `FileInterceptor('file')` and parses a single multipart
|
|
part named `file`.
|
|
* File size is validated against `UPLOAD_SIZE_LIMIT` through
|
|
`parseUploadSizeLimit()` in `backend/src/common/utils/upload.ts`.
|
|
* Category icons are written under `UPLOAD_DIR/icons`; when `categoryId` is
|
|
supplied, the image is cropped to a 128-by-128 PNG and exposed at
|
|
`/uploads/icons/{categoryId}.png`.
|
|
* Challenge attachments are written under `UPLOAD_DIR/challenges` and exposed
|
|
at `/uploads/challenges/{safeFilename}`.
|
|
* Site logos are decoded with Sharp and accepted only when their actual format
|
|
is PNG, JPEG, GIF, or WebP. The multipart MIME type and filename extension
|
|
are not trusted for validation.
|
|
* An accepted logo keeps its sanitized filename, is written directly under
|
|
`UPLOAD_DIR`, and returns `{ publicUrl, originalFilename }`, with `publicUrl`
|
|
in the form `/uploads/{safeFilename}`.
|
|
* Category icons are decoded with Sharp and normalized to a 128-by-128 PNG.
|
|
When `categoryId` is supplied, the file is written to
|
|
`UPLOAD_DIR/icons/{categoryId}.png` (overwriting any previous icon for that
|
|
category) and the response carries `{ id: "{categoryId}.png", publicUrl,
|
|
width: 128, height: 128, mimeType: "image/png", storedPath, size,
|
|
originalFilename }`.
|
|
* Category-icon validation is strict: a missing `file`, an oversized upload,
|
|
a corrupt/non-image payload, or a buffer that Sharp cannot decode returns
|
|
`400 Bad Request`. Invalid category-icon payloads use the message
|
|
`Category icon must be a valid PNG, JPEG, GIF, or WebP image.` and **are
|
|
not persisted** — the existing icon file (if any) on disk is left untouched.
|
|
The multipart MIME type, the filename extension, and any browser `accept`
|
|
metadata are not trusted as proof of validity.
|
|
* A missing `file` part, an oversized upload, a corrupt image, or a decoded logo
|
|
in another format returns `400 Bad Request`. Invalid logo payloads use the
|
|
message `Logo must be a valid PNG, JPEG, GIF, or WebP image.` and are not
|
|
persisted.
|
|
|
|
# Examples
|
|
|
|
1. Sign in as an administrator and navigate to `/admin/general`.
|
|
2. In the site-logo setting, choose a PNG, JPEG, GIF, or WebP image and save the
|
|
general settings.
|
|
3. The client uploads the selected file to `POST /api/v1/uploads/logo` before
|
|
saving the returned `/uploads/...` URL as the configured logo.
|
|
4. After a successful save, the uploaded image is available from its returned
|
|
public URL and is used wherever the configured site logo is rendered.
|
|
5. Choosing a corrupt file, or a valid image in an unsupported format, leaves
|
|
the file unstored and surfaces the upload validation failure to the user.
|
|
|
|
# Key files
|
|
|
|
| File | Responsibility |
|
|
|---|---|
|
|
| `backend/src/modules/uploads/uploads.controller.ts` | Registers upload routes, validates payloads, and persists files. |
|
|
| `backend/src/common/utils/upload.ts` | Parses size limits and sanitizes upload filenames. |
|
|
| `frontend/src/app/features/admin/general/admin-general.component.ts` | Provides the administrator-facing logo selection and settings workflow. |
|
|
| `tests/backend/uploads-logo.spec.ts` | Verifies accepted logo formats, spoofed/corrupt payload rejection, size limits, and persisted responses. |
|
|
|
|
# See also
|
|
|
|
- [Admin — General Settings](/guides/admin-general-settings.md)
|
|
- [REST API Overview](/api/rest-overview.md)
|
|
- [Backend Module Map](/architecture/backend-modules.md)
|