-
Notifications
You must be signed in to change notification settings - Fork 0
[TM-2769] add bulk upload media #478
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f3e1a4c
54f86a3
682b756
50723b8
cb65c04
967105a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { IsString } from "class-validator"; | ||
| import { ApiProperty } from "@nestjs/swagger"; | ||
| import { JsonApiDto } from "@terramatch-microservices/common/decorators"; | ||
|
|
||
| @JsonApiDto({ type: "mediaBulkResponse" }) | ||
| export class MediaBulkResponseDto { | ||
| constructor(index: number, error: string) { | ||
| this.index = index; | ||
| this.error = error; | ||
| } | ||
|
|
||
| @IsString() | ||
| @ApiProperty({ description: "The index of the media" }) | ||
| index: number; | ||
|
|
||
| @IsString() | ||
| @ApiProperty({ description: "The error message" }) | ||
| error: string; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { ApiProperty } from "@nestjs/swagger"; | ||
| import { CreateDataDto, JsonApiBulkBodyDto } from "@terramatch-microservices/common/util/json-api-update-dto"; | ||
| import { IsBoolean, IsNumber, IsOptional, IsString, IsUrl } from "class-validator"; | ||
|
|
||
| export class MediaRequestBulkAttributes { | ||
| @IsString() | ||
| @IsUrl() | ||
| @ApiProperty({ required: true, description: "The URL of the media" }) | ||
| downloadUrl: string; | ||
|
|
||
| @IsBoolean() | ||
| @ApiProperty({ description: "Whether the media is public" }) | ||
| isPublic: boolean; | ||
|
|
||
| @IsNumber() | ||
| @IsOptional() | ||
| @ApiProperty({ type: Number, nullable: true, description: "The latitude of the media" }) | ||
| lat: number | null; | ||
|
|
||
| @IsNumber() | ||
| @IsOptional() | ||
| @ApiProperty({ type: Number, nullable: true, description: "The longitude of the media" }) | ||
| lng: number | null; | ||
| } | ||
|
|
||
| export class MediaRequestBulkBody extends JsonApiBulkBodyDto( | ||
| class MediaRequestBulkData extends CreateDataDto("media", MediaRequestBulkAttributes) {}, | ||
| { | ||
| minSize: 1, | ||
| minSizeMessage: "At least one media must be provided", | ||
| description: "Array of media to create", | ||
| example: [ | ||
| { type: "media", attributes: { isPublic: true, downloadUrl: "https://example.com/image.jpg" } }, | ||
| { type: "media", attributes: { isPublic: false, downloadUrl: "https://example.com/image.jpg" } } | ||
| ] | ||
| } | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { ApiProperty } from "@nestjs/swagger"; | ||
| import { IsUUID } from "class-validator"; | ||
|
|
||
| export class SiteMediaBulkUploadDto { | ||
| @IsUUID() | ||
| @ApiProperty({ description: "Site UUID to upload media to" }) | ||
| siteUuid: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,11 @@ import { getBaseEntityByLaravelTypeAndId } from "./processors/media-owner-proces | |
| import { MediaUpdateBody } from "@terramatch-microservices/common/dto/media-update.dto"; | ||
| import { SingleMediaDto } from "./dto/media-query.dto"; | ||
| import { EntityType } from "@terramatch-microservices/database/constants/entities"; | ||
| import { SiteMediaBulkUploadDto } from "./dto/site-media-bulk-upload.dto"; | ||
| import { Site } from "@terramatch-microservices/database/entities/site.entity"; | ||
| import { MediaRequestBulkBody } from "./dto/media-request-bulk.dto"; | ||
| import { MediaBulkResponseDto } from "./dto/media-bulk-response.dto"; | ||
| import { Media } from "@terramatch-microservices/database/entities/media.entity"; | ||
|
|
||
| @Controller("entities/v3/files") | ||
| export class FilesController { | ||
|
|
@@ -59,6 +64,75 @@ export class FilesController { | |
| return this.entitiesService.mediaDto(media, { entityType: media.modelType as EntityType, entityUuid: model.uuid }); | ||
| } | ||
|
|
||
| @Post("/site/:siteUuid/bulkUpload") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be I would say this bulk upload should either be made generic so that it can be done for any entity / collection, or this photo upload bulk process should use that same client side pattern being used in the link above. If you decide to make this endpoint generic for all entities and want help getting the body parameter to work correctly with two different possible types, let me know. I imagine that will be a little tricky to wire up.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh... I just remembered this is for Flority. Does this ticket need to be in the current release? I want to make the current endpoint more adaptable (perhaps it only accepts "bulk" uploads, but sometimes the client only sends a single file, which might be simpler), but I think that's going to be enough additional work that it will delay the release if we try to include it. I'm going to hold off on reviewing the rest of this PR for now. |
||
| @ApiOperation({ | ||
| operationId: "siteMediaBulkUpload", | ||
| summary: "Upload multiple files to a site photos collection", | ||
| description: "Upload multiple files to a site photos collection" | ||
| }) | ||
| @ExceptionResponse(UnauthorizedException, { | ||
| description: "Authentication failed, or site unavailable to current user." | ||
| }) | ||
| @ExceptionResponse(NotFoundException, { description: "Site not found." }) | ||
| @ExceptionResponse(BadRequestException, { description: "Invalid request." }) | ||
| @JsonApiResponse([MediaDto, MediaBulkResponseDto]) | ||
| async siteMediaBulkUpload(@Param() { siteUuid }: SiteMediaBulkUploadDto, @Body() payload: MediaRequestBulkBody) { | ||
| const site = await Site.findOne({ where: { uuid: siteUuid }, attributes: ["id", "frameworkKey", "projectId"] }); | ||
| if (site == null) { | ||
| throw new NotFoundException(`Site with UUID ${siteUuid} not found`); | ||
| } | ||
| await this.policyService.authorize("uploadFiles", site); | ||
| const errors: MediaBulkResponseDto[] = []; | ||
| const createdMedias: Media[] = []; | ||
| await this.fileUploadService.transaction(async transaction => { | ||
| for (const [index, payloadData] of payload.data.entries()) { | ||
| let file: Express.Multer.File; | ||
| try { | ||
| file = await this.fileUploadService.fetchDataFromUrlAsMulterFile(payloadData.attributes.downloadUrl); | ||
| const media = await this.fileUploadService.uploadFile( | ||
| site, | ||
| "sites", | ||
| "photos", | ||
| file, | ||
| payloadData.attributes, | ||
| transaction | ||
| ); | ||
| createdMedias.push(media); | ||
| } catch (error) { | ||
| errors.push(new MediaBulkResponseDto(index, error.message)); | ||
| } | ||
| } | ||
| if (errors.length > 0) { | ||
| // clear s3 files | ||
| for (const media of createdMedias) { | ||
| await this.mediaService.deleteMediaFromS3(media); | ||
| } | ||
| throw new BadRequestException("Failed to upload some files"); | ||
| } | ||
| }); | ||
| let document; | ||
| if (errors.length > 0) { | ||
| document = buildJsonApi(MediaBulkResponseDto); | ||
| for (const error of errors) { | ||
| document.addData(error.index.toString(), new MediaBulkResponseDto(error.index, error.error)); | ||
| } | ||
| } else { | ||
| document = buildJsonApi(MediaDto); | ||
| for (const media of createdMedias) { | ||
| document.addData( | ||
| media.uuid, | ||
| new MediaDto(media, { | ||
| url: this.mediaService.getUrl(media), | ||
| thumbUrl: this.mediaService.getUrl(media, "thumbnail"), | ||
| entityType: "sites", | ||
| entityUuid: site.uuid | ||
| }) | ||
| ); | ||
| } | ||
| } | ||
| return document; | ||
| } | ||
|
|
||
| @Post("/:entity/:uuid/:collection") | ||
| @ApiOperation({ | ||
| operationId: "uploadFile", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resource types should be plural:
mediaBulkResponses