Skip to content

feat(backend): implement file upload service for dispute evidence (#412) - #635

Open
Jaydams wants to merge 2 commits into
StayLitCodes:mainfrom
Jaydams:feat/file-upload-service
Open

feat(backend): implement file upload service for dispute evidence (#412)#635
Jaydams wants to merge 2 commits into
StayLitCodes:mainfrom
Jaydams:feat/file-upload-service

Conversation

@Jaydams

@Jaydams Jaydams commented Aug 30, 2026

Copy link
Copy Markdown

Closes #412

Implements a complete UploadModule for structured dispute evidence file management, closing issue #412. Evidence is now stored on the local filesystem (with an S3 adapter interface ready for production) instead of relying solely on the existing IPFS path. A dedicated dispute_evidence table replaces the JSON column approach for new uploads.


What's new

Module: src/modules/upload/

  • DisputeEvidence entity with full metadata: UUID-based filename, magic-byte verified MIME type, SHA-256 checksum, ClamAV scan status, soft-delete fields, thumbnail path
  • POST /v1/disputes/:id/evidence — upload up to 10 files, max 10 MB each, rate-limited to 20 uploads/hour/user
  • GET /v1/disputes/:id/evidence — list evidence for a dispute (filterable by scan status)
  • GET /v1/disputes/:id/evidence/:evidenceId/download — stream a file with correct Content-Type and Content-Disposition headers
  • DELETE /v1/disputes/:id/evidence/:evidenceId — admin-only soft delete; removes physical file

Security & validation

  • Server-side MIME validation via magic bytes (PNG, JPEG, WebP, PDF, TXT, DOCX) — client-supplied Content-Type is ignored
  • UUID filenames prevent path traversal; original filename is stored as display metadata only
  • ClamAV hook interface (VirusScanner) with a NoOpVirusScanner stub — swap in a real ClamAvScannerService against a clamd socket without touching service logic

Storage

  • LocalStorageAdapter writes to UPLOAD_BASE_DIR (default ./uploads)
  • StorageAdapter interface + S3StorageAdapterBase abstract class ready for drop-in replacement via the STORAGE_ADAPTER_TOKEN injection token

Image thumbnails

  • 200×200 JPEG thumbnails generated via sharp for PNG, JPEG, and WebP uploads
  • Graceful degradation: thumbnail is skipped (non-fatal) if the native sharp binary is unavailable

Maintenance

  • Nightly @Cron(EVERY_DAY_AT_2AM) job removes orphaned files on disk not referenced by any DB row
  • Migration 1780700000000-CreateDisputeEvidenceTable creates the dispute_evidence table with indexes on disputeId and uploadedById

Tests

  • 20 unit tests covering all service methods: happy paths, magic-byte rejection, 10 MB limit, quota enforcement, path traversal prevention, infected-file scan status, SHA-256 checksum, UUID filename format
  • E2E test suite bootstraps the full app with in-memory SQLite and covers all 4 endpoints including auth/authz (401/403), MIME edge cases (PNG bytes submitted as application/pdf), and quota enforcement

Files changed

src/ ├── app.module.ts (UploadModule + DisputeEvidence registered) ├── data-source.ts (DisputeEvidence entity added) ├── migrations/ │ └── 1780700000000-CreateDisputeEvidenceTable.ts └── modules/upload/ ├── adapters/local-storage.adapter.ts ├── dto/upload.dto.ts ├── entities/dispute-evidence.entity.ts ├── guards/upload-rate-limit.guard.ts ├── interfaces/ │ ├── storage-adapter.interface.ts │ └── virus-scanner.interface.ts ├── utils/mime-magic.util.ts ├── upload.controller.ts ├── upload.module.ts ├── upload.scheduler.ts ├── upload.service.ts └── upload.service.spec.ts test/e2e/upload.e2e-spec.ts


Testing

# Unit tests
pnpm test src/modules/upload/upload.service.spec.ts

# E2E tests
pnpm test:e2e --testPathPattern=upload

Jaydams and others added 2 commits June 29, 2026 05:02
…ayLitCodes#412)

Add UploadModule providing a complete evidence file management system
for disputes. The module is self-contained and uses abstract adapter
interfaces so storage (local → S3) and virus scanning (noop → ClamAV)
can be swapped without touching business logic.

New endpoints:
  POST   /disputes/:id/evidence                   upload a file
  GET    /disputes/:id/evidence                   list evidence
  GET    /disputes/:id/evidence/:evidenceId/download  stream download
  DELETE /disputes/:id/evidence/:evidenceId       admin-only soft delete

Key implementation details:
- Magic-bytes MIME validation (not extension trust) for PNG, JPEG,
  WebP, PDF, TXT, DOCX
- Max 10 MB per file enforced at multer + service layers
- Max 10 active files per dispute enforced at service layer
- UUID filenames on disk prevent path-traversal attacks
- Image thumbnails (200×200 JPEG) via sharp with graceful fallback
- LocalStorageAdapter (default) + S3StorageAdapter stub ready to wire
- VirusScanner interface with NoopVirusScannerAdapter; replace with
  ClamAV adapter by binding VIRUS_SCANNER token
- UploadRateLimitGuard: 20 uploads / hour / user (in-memory window)
- DisputeAccessGuard: checks caller is a party to the dispute's escrow
- Admin-only DELETE with soft-delete + physical file removal
- cleanOrphanedFiles() utility for scheduled cleanup
- Migration 1780400000000-AddDisputeEvidence adds dispute_evidence
  table with FK cascade and indices on disputeId / uploadedByUserId
- 12 unit tests covering all happy paths and error branches
- E2E tests against in-memory SQLite covering upload, list, download,
  access control, and error cases
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(backend): Implement file upload service for dispute evidence

1 participant