/v1/trainee/uploads, router traineeUploadsRouter in src/modules/uploads/uploads.routes.ts, service createUploadsService in uploads.service.ts.
Lane: trainee. The router mounts authenticateTrainee only, without the app access guard. A preview token is refused because the only route is a POST. See Authentication.
Endpoints
POST /v1/trainee/uploads
Stores one file and returns its public URL.
Auth: trainee token.
The body is the raw file bytes. It is not multipart and not base64.
string
required
The file’s media type, for example
image/jpeg or video/mp4. See the size note below for why this should not be application/octet-stream or application/json.string
default:"application/octet-stream"
The media type to store the object with. The app sends the same value as
Content-Type.string
default:"upload"
The original file name. Only its extension is used.
Size limits
The route’s own parser isexpress.raw with type: () => true and a limit of 15 MB. Two global parsers in app.ts run before it and win for their content types:
A body over the limit returns status 413 with code
BAD_REQUEST and message payload too large.
The server does not check the media type against an allow-list and does not inspect the bytes, apart from HEIC detection.
What the service does
- Requires R2 storage to be configured.
- Detects HEIC images from the file bytes and converts them to JPEG, because coach browsers cannot display HEIC. If the conversion fails, the original is stored and a warning is logged.
- Picks the extension:
jpgafter a HEIC conversion. Otherwise the extension ofx-file-namewhen it is 1 to 8 lower-case letters or digits. Otherwise from the media type:jpg,png,webp,gif,mp4ormov. Otherwisebin. - Stores the object under
media/<uuid>.<ext>, prefixed withR2_PATH_PREFIXwhen set, with the media type as its content type. - Returns the public URL built from
R2_PUBLIC_BASE_URLand the key.
R2_ENDPOINT, R2_REGION, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_PUBLIC_BASE_URL and R2_PATH_PREFIX.
Uploads are not tied to the trainee in the database. No row is written until the URL is used by another endpoint, and unused objects are not cleaned up by this module.
Response: 200.