Skip to content

BC-11419 - Add upload for temporary files#196

Open
bischofmax wants to merge 16 commits intomainfrom
BC-11419
Open

BC-11419 - Add upload for temporary files#196
bischofmax wants to merge 16 commits intomainfrom
BC-11419

Conversation

@bischofmax
Copy link
Contributor

Description

Links to Tickets or other pull requests

Screenshots of UI changes

Approval for review

  • DEV: If the API or client code was changed, all necessary code generation or synchronization steps were completed and tested.
  • QA: In addition to review, the code has been manually tested (if manual testing is possible)
  • All points were discussed with the ticket creator, support-team or product owner. The code upholds all quality guidelines from the PR-template.

@bischofmax bischofmax self-assigned this Mar 26, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for uploading “temporary” files in the files-storage module by introducing a storageDirectory concept (currently TEMP), adjusting persistence/pathing, and exposing an expiry timestamp to clients.

Changes:

  • Add StorageDirectory (TEMP) and propagate it through DTOs/factories/service into FileRecord + DB entity.
  • Add a new POST /file/temp/upload/... endpoint that uploads into the TEMP directory.
  • Expose expiresAt in FileRecordResponse and update record lookup to include TEMP records.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/modules/files-storage/testing/file-dto.test.factory.ts Updates test DTO factory to include the new storageDirectory field.
src/modules/files-storage/repo/file-record.repo.ts Changes findOneById filtering to include TEMP records.
src/modules/files-storage/repo/file-record.entity.ts Persists storageDirectory on the Mongo entity.
src/modules/files-storage/repo/file-record-scope.ts Adds byNotDeletedOrTemp() scope used for TEMP record retrieval.
src/modules/files-storage/domain/service/files-storage.service.ts Passes storageDirectory into FileRecordFactory.buildFromExternalInput during upload.
src/modules/files-storage/domain/file-record.do.ts Introduces StorageDirectory, adds getExpiresAt(), and prefixes S3 paths with storageDirectory.
src/modules/files-storage/domain/factory/pass-through-file-dto.factory.ts Carries storageDirectory into pass-through DTOs.
src/modules/files-storage/domain/factory/file-record.factory.ts Allows building a FileRecord with an optional storageDirectory.
src/modules/files-storage/domain/factory/file-dto.factory.ts Extends FileDtoFactory.create to accept storageDirectory.
src/modules/files-storage/domain/dto/pass-through-file.dto.ts Adds storageDirectory to PassThroughFileDto.
src/modules/files-storage/domain/dto/file.dto.ts Adds storageDirectory to FileDto.
src/modules/files-storage/api/uc/files-storage.uc.ts Adds tempUpload() and threads storageDirectory through busboy upload.
src/modules/files-storage/api/mapper/file-dto.mapper.ts Maps busboy file info into FileDto with optional storageDirectory.
src/modules/files-storage/api/dto/file-storage.response.ts Adds expiresAt to the API response model.
src/modules/files-storage/api/controller/files-storage.controller.ts Adds the new temporary upload route.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

public byNotDeletedOrTemp(): this {
this.addQuery({ $or: [{ deletedSince: null }, { storageDirectory: StorageDirectory.TEMP }] });
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

byNotDeletedOrTemp() will return TEMP records regardless of deletedSince. This means a TEMP file that is explicitly deleted (or otherwise marked) can still be fetched via findOneById, undermining delete semantics. If TEMP files need to be readable only until expiry, consider filtering TEMP by an explicit expiry window/field (or excluding those additionally marked-for-delete).

Suggested change
this.addQuery({ $or: [{ deletedSince: null }, { storageDirectory: StorageDirectory.TEMP }] });
this.addQuery({ deletedSince: null });

Copilot uses AI. Check for mistakes.
Comment on lines 18 to 21
public async findOneById(id: EntityId): Promise<FileRecord> {
const scope = new FileRecordScope().byFileRecordId(id).byMarkedForDelete(false);
const scope = new FileRecordScope().byFileRecordId(id).byNotDeletedOrTemp();
const fileRecord = await this.findOneOrFail(scope);

Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findOneById() now includes TEMP records via byNotDeletedOrTemp(), but other repo methods (e.g. findMultipleById, findByParentId) still use byMarkedForDelete(false) and will exclude TEMP records because TEMP sets deletedSince. This inconsistency can lead to surprising behavior where single-id operations work but multi-id/parent-based operations silently omit temp uploads. Consider introducing explicit "includeTemp" variants or aligning the filtering strategy across read APIs.

Copilot uses AI. Check for mistakes.
Comment on lines +211 to +220
public getExpiresAt(): Date | undefined {
if (!this.isTempFile() || !this.props.deletedSince) {
return;
}

const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
const expiresAt = new Date(this.props.deletedSince.getTime() + sevenDaysInMs);

return expiresAt;
}
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New TEMP behavior (StorageDirectory, getExpiresAt()) is not covered by the existing file-record.do.spec.ts tests. Please add unit tests covering at least: (1) TEMP records produce an expiresAt based on the chosen retention, and (2) non-TEMP records return undefined.

Copilot uses AI. Check for mistakes.
Comment on lines 360 to 363
public createPath(): string {
const path = [this.props.storageLocationId, this.id].join('/');
const path = [this.props.storageDirectory, this.props.storageLocationId, this.id].filter(Boolean).join('/');

return path;
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createPath() now conditionally prefixes storageDirectory, but there is no test asserting the TEMP path shape (e.g. temp/<storageLocationId>/<id>). Adding a unit test for TEMP paths would help prevent accidental breaking changes to S3 key layout.

Copilot uses AI. Check for mistakes.
@ApiResponse({ status: 400, type: BadRequestException })
@ApiResponse({ status: 403, type: ForbiddenException })
@ApiResponse({ status: 500, type: InternalServerErrorException })
@ApiConsumes('multipart/form-data')
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new streaming temp upload endpoint does not apply CurrentUploadMetricsInterceptor, while the normal multipart upload endpoint does. If upload metrics/limits rely on that interceptor, temp uploads will be missing from dashboards and alerts. Consider adding the same interceptor here for parity.

Suggested change
@ApiConsumes('multipart/form-data')
@ApiConsumes('multipart/form-data')
@UseInterceptors(CurrentUploadMetricsInterceptor)

Copilot uses AI. Check for mistakes.
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
68.0% Coverage on New Code (required ≥ 95%)
8 Uncovered Lines on New Code (required ≤ 0)

See analysis details on SonarQube Cloud

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.

4 participants