5.4 KiB
5.4 KiB
Implementation Plan: Admin Area General Settings and Categories 1.03
1. Architectural Reconnaissance
- Codebase style & conventions: TypeScript monorepo with a NestJS 10 REST backend and Angular 17 standalone components. Upload requests are encapsulated in
AdminService;AdminGeneralComponentuses reactive forms, signals,asynchandlers, and inlinedata-testidfeedback. Backend upload handling currently lives directly inUploadsController, uses Multer memory buffers,sharp, collision-resistantsafeFilename, and the global exception filter's standard{ code, message, path, timestamp }envelope. The Job is not already implemented:handleLogoUploadonly checks presence/size before writing raw bytes, while the existing frontend error branch is never reached for malformed data. - Data Layer: SQLite through TypeORM for settings, but this fix does not require a schema or migration. Logo bytes are stored under configured
UPLOAD_DIR(default/data/hipctf/uploads) and become active only after the returned URL is assigned to the form and settings are saved. - Test Framework & Structure: Root Jest 29/ts-jest multi-project configuration. Backend endpoint tests live in
tests/backend, use Nest's real application, in-memory SQLite, Supertest, CSRF/auth setup, and an isolated upload directory; frontend logic tests live intests/frontendwith jsdom. All tests run with rootnpm test, with focused backend runs available throughnpm run test:backend. - Required Tools & Dependencies: No new system tool, global CLI, package, or
setup.shchange is required. Reuse the already installedsharpdependency to decode the full image and obtain authoritative format metadata rather than trusting the multipart MIME type, extension, or magic bytes alone. Existing setup already installs native dependencies, prepares/data/hipctf/uploads, and builds both workspaces.
2. Impacted Files
- To Modify:
backend/src/modules/uploads/uploads.controller.ts— make logo handling asynchronous; decode and validate the complete image against an explicit logo format policy before creating a filename or writing bytes.tests/backend/uploads-logo.spec.ts— replace the header-only pseudo-PNG success fixture with a genuinely decodable image and add focused missing/malformed/unsupported rejection assertions, including proof that rejected payloads create no files.
- To Create: None.
3. Proposed Changes
- Database / Schema Migration: No database change. Invalid uploads must never return a URL or mutate the General form's existing
logocontrol, so the persisted setting and subsequent bootstrappageLogoremain unchanged. - Backend Logic & APIs:
- Convert
handleLogoUploadto return a promise and await it fromuploadLogo. - Retain the current missing-file and configured-size checks, then call
sharp(file.buffer).metadata()(or an equivalent full decode operation) before any filesystem side effect. Treat decode failures, absent format/dimensions, truncated/corrupt images, and formats outside an explicit configured logo allowlist as400 BadRequestException/VALIDATION_FAILEDwith a stable, user-readable message such asLogo must be a valid PNG, JPEG, GIF, or WebP image. - Define the logo policy beside the handler as a readonly set/literal union so accepted formats are auditable. Base acceptance on Sharp's detected format, not
file.mimetype, original extension, or browseracceptfiltering. Keep the current upload-size policy and collision-resistant filename behavior for accepted files. - Perform validation before
mkdir/writeFileSync; only valid bytes reachUPLOAD_DIR. Preserve the response contract{ publicUrl, originalFilename }and existing 201 status for valid uploads.
- Convert
- Frontend UI Integration: No frontend code change is expected.
AdminGeneralComponent.onLogoFileChangealready clears the prior error, updatesform.controls.logoonly after a successfuluploadLogoresponse, catches the server's standard error message intologoUploadError, and resetsuploadingLogoinfinally. Once the backend rejects malformed payloads,[data-testid="general-logo-error"]will render, the previous hidden[data-testid="general-logo"]value will be preserved, and the form remains usable. Do not add client-only validation as a security boundary;accept="image/*"remains a picker hint.
4. Test Strategy
- Target Unit Test File: Modify
tests/backend/uploads-logo.spec.ts; no broad component harness or UI/E2E suite is needed because the existing component's success-only mutation and catch/finally behavior already provides the required preservation/feedback flow. - Mocking Strategy: Use no mocks for image validation or filesystem behavior. Generate a tiny valid PNG in-memory with the existing
sharppackage for the core success test. Through the authenticated Supertest endpoint, assert: missing multipartfilereturns 400; representative malformed/unsupported inputs (plain text with a misleading image name/MIME, a truncated GIF, and an eight-zero-byte PNG sent asimage/png) return 400 with the standard validation envelope; and the upload directory listing is unchanged after each rejection. Keep one valid-image assertion proving a URL is returned, the file is persisted/fetchable, and cleanup remains hermetic. Run all coverage from the repository root withnpm test; during implementation also run the existing root build/typecheck (npm run build) since no standalone lint script is defined.