universo-platform-3d
870 строк · 26.4 Кб
1import {
2BadRequestException,
3Body,
4Controller,
5Delete,
6Get,
7Optional,
8Param,
9ParseIntPipe,
10Patch,
11Post,
12Query,
13Res,
14UploadedFile,
15UseGuards,
16UseInterceptors,
17UsePipes,
18ValidationPipe
19} from '@nestjs/common'
20import { FileInterceptor } from '@nestjs/platform-express'
21import {
22ApiBody,
23ApiConsumes,
24ApiCreatedResponse,
25ApiOkResponse,
26ApiParam,
27ApiProperty,
28ApiQuery
29} from '@nestjs/swagger'
30import { plainToInstance } from 'class-transformer'
31import { validateOrReject } from 'class-validator'
32import { Types } from 'mongoose'
33import { FirebaseTokenAuthGuard } from '../auth/auth.guard'
34import { UserToken } from '../auth/get-user.decorator'
35import { PublicFirebaseAuthNotRequired } from '../auth/public.decorator'
36import { ASSET_TYPE } from '../option-sets/asset-type'
37import {
38FileUploadApiResponse,
39FileUploadPublicApiResponse
40} from '../util/file-upload/file-upload'
41import { AssetId, UserId } from '../util/mongo-object-id-helpers'
42import {
43PaginatedResponse,
44PaginationInterface
45} from './../util/pagination/pagination.interface'
46import { AssetApiResponse, AssetUsageApiResponse } from './asset.models'
47import { Asset } from './asset.schema'
48import { AssetService } from './asset.service'
49import {
50CreateAssetDto,
51CreateMapDto,
52CreateMaterialDto,
53CreateTextureDto
54} from './dto/create-asset.dto'
55import {
56PaginatedSearchAssetDtoV2,
57getPopulateFieldsFromPaginatedSearchAssetDto
58} from './dto/paginated-search-asset.dto'
59import { SearchAssetDto } from './dto/search-asset.dto'
60import {
61AddAssetPurchaseOptionDto,
62UpdateAssetDto
63} from './dto/update-asset.dto'
64import { PopulateField } from '../util/pagination/pagination.service'
65import { TAG_TYPES } from '../tag/models/tag-types.enum'
66import { AddTagToAssetDto } from './dto/add-tag-to-asset.dto'
67import { UpdateAssetTagsDto } from './dto/update-asset-tags.dto'
68import { IncludeSoftDeletedAssetDto } from './dto/include-soft-deleted-asset.dto'
69import { DomainOrAuthUserGuard } from '../space/guards/DomainOrAuthUserGuard.guard'
70import { GetAssetsPriceDto } from './dto/assets-price.dto'
71import { Response } from 'express'
72
73/**
74* @description Swagger generation doesn't support generics, so for each paginated response, it has to extended the PaginatedResponse class and implement the PaginationInterface
75* @date 2023-07-10 16:33
76*/
77export class AssetFullDataPaginatedResponse
78extends PaginatedResponse
79implements PaginationInterface
80{
81@ApiProperty({ type: [Asset] })
82data: Asset[]
83}
84@UsePipes(new ValidationPipe({ whitelist: false }))
85@Controller('asset')
86export class AssetController {
87constructor(private readonly assetService: AssetService) {}
88
89/*****************************
90PUBLICLY ACCESSIBLE ENDPOINTS
91****************************/
92
93/** Search for an asset */
94@PublicFirebaseAuthNotRequired()
95@Get('search')
96@ApiOkResponse({ type: [AssetApiResponse] })
97public async search(@Query() searchAssetDto: PaginatedSearchAssetDtoV2) {
98return await this.assetService.searchAssetsPublic(searchAssetDto, true)
99}
100
101/**
102* @description Same as above but no populate
103* @date 2023-07-12 23:49
104*/
105@PublicFirebaseAuthNotRequired()
106@Get('search-v2')
107@ApiOkResponse({ type: [AssetApiResponse] })
108public async searchV2(@Query() searchAssetDto: PaginatedSearchAssetDtoV2) {
109return await this.assetService.searchAssetsPublic(searchAssetDto, false)
110}
111
112/***********************
113AUTH REQUIRED ENDPOINTS
114**********************/
115
116/**
117* Get Mirror Library assets
118* TODO this needs to be paginated
119*/
120@Get('library')
121@FirebaseTokenAuthGuard()
122@ApiOkResponse({ type: [AssetApiResponse] })
123@ApiQuery({ required: false })
124public async getMirrorPublicLibraryAssets(
125@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
126) {
127return await this.assetService.findMirrorPublicLibraryAssets(
128searchAssetDto,
129undefined,
130true
131)
132}
133
134/**
135* @description Same as getMirrorPublicLibraryAssets, but doesn't populate the fields
136* @date 2023-07-12 23:33
137*/
138@Get('library-v2')
139@ApiOkResponse({ type: [AssetApiResponse] })
140@FirebaseTokenAuthGuard()
141@ApiQuery({ required: false })
142public async getMirrorPublicLibraryAssetsV2(
143@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
144) {
145return await this.assetService.findMirrorPublicLibraryAssets(
146searchAssetDto,
147undefined,
148false
149)
150}
151
152@Post()
153@FirebaseTokenAuthGuard()
154@ApiCreatedResponse({ type: AssetApiResponse })
155@ApiBody({
156type: CreateAssetDto
157})
158public async create(
159@UserToken('user_id') userId: UserId,
160@Body()
161createAssetDto: CreateAssetDto &
162CreateMaterialDto &
163CreateTextureDto &
164CreateMapDto // See https://www.loom.com/share/7e09d2777ef94368bcd5fd8c8341b5ef for walkthrough of DTOs with discriminators
165) {
166// check whether to use the subclass as determined by assetType
167switch (createAssetDto.assetType) {
168case ASSET_TYPE.MATERIAL:
169const dtoMaterial = plainToInstance(CreateMaterialDto, createAssetDto)
170try {
171await validateOrReject(dtoMaterial)
172} catch (error) {
173console.error(error.join(','))
174throw new BadRequestException(error.join(','))
175}
176return await this.assetService.createMaterial({
177ownerId: userId,
178...dtoMaterial
179})
180case ASSET_TYPE.TEXTURE:
181const dtoTexture = plainToInstance(CreateTextureDto, createAssetDto)
182try {
183await validateOrReject(dtoTexture)
184} catch (error) {
185console.error(error.join(','))
186throw new BadRequestException(error.join(','))
187}
188return await this.assetService.createTexture({
189ownerId: userId,
190...dtoTexture
191})
192case ASSET_TYPE.MAP:
193const dtoMap = plainToInstance(CreateMapDto, createAssetDto)
194try {
195await validateOrReject(dtoMap)
196} catch (error) {
197console.error(error.join(','))
198throw new BadRequestException(error.join(','))
199}
200return await this.assetService.createMap({
201ownerId: userId,
202...dtoMap
203})
204default:
205const dtoAsset = plainToInstance(CreateAssetDto, createAssetDto)
206try {
207await validateOrReject(dtoAsset)
208} catch (error) {
209console.error(error.join(','))
210throw new BadRequestException(error.join(','))
211}
212return await this.assetService.createAsset({
213ownerId: userId,
214...dtoAsset
215})
216}
217}
218
219@Get('recents')
220@FirebaseTokenAuthGuard()
221@ApiOkResponse({ type: [AssetApiResponse] })
222public async getUserRecentInstancedAssets(
223@UserToken('user_id') userId: UserId,
224@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
225) {
226return await this.assetService.getRecentInstancedAssets(
227userId,
228searchAssetDto
229)
230}
231
232@Post('new')
233@FirebaseTokenAuthGuard()
234@ApiConsumes('multipart/form-data')
235@ApiBody({ schema: { type: 'file' } })
236@ApiCreatedResponse({ type: AssetApiResponse })
237@UseInterceptors(
238FileInterceptor('file', {
239limits: { fieldSize: 20 * 1024 * 1024, fileSize: 20 * 1024 * 1024 } // 20MB limit. Arbitrary. I wanted to reference a class property of this.fileSizeBytes, but not sure if that's accessible in a decorator
240})
241)
242public async createWithUpload(
243@UserToken('user_id') userId: UserId,
244@Body()
245createAssetDto:
246| CreateAssetDto
247| CreateMaterialDto
248| CreateTextureDto
249| CreateMapDto, // See https://www.loom.com/share/7e09d2777ef94368bcd5fd8c8341b5ef for walkthrough of DTOs with discriminators
250@UploadedFile() file: Express.Multer.File
251) {
252// check whether to use the subclass as determined by assetType
253switch (createAssetDto.assetType) {
254case ASSET_TYPE.MATERIAL:
255const dtoMaterial = plainToInstance(CreateMaterialDto, createAssetDto)
256try {
257await validateOrReject(dtoMaterial)
258} catch (error) {
259console.error(error.join(','))
260throw new BadRequestException(error.join(','))
261}
262return await this.assetService.createMaterialWithUpload(
263{ ownerId: userId, ...dtoMaterial },
264file
265)
266case ASSET_TYPE.TEXTURE:
267const dtoTexture = plainToInstance(CreateTextureDto, createAssetDto)
268try {
269await validateOrReject(dtoTexture)
270} catch (error) {
271console.error(error.join(','))
272throw new BadRequestException(error.join(','))
273}
274return await this.assetService.createTextureWithUpload(
275{ ownerId: userId, ...dtoTexture },
276file
277)
278case ASSET_TYPE.MAP:
279const dtoMap = plainToInstance(CreateMapDto, createAssetDto)
280try {
281await validateOrReject(dtoMap)
282} catch (error) {
283console.error(error.join(','))
284throw new BadRequestException(error.join(','))
285}
286return await this.assetService.createMapWithUpload(
287{ ownerId: userId, ...dtoMap },
288file
289)
290// Default to base Asset class
291default:
292const dtoAsset = plainToInstance(CreateAssetDto, createAssetDto)
293try {
294await validateOrReject(dtoAsset)
295} catch (error) {
296console.error(error.join(','))
297throw new BadRequestException(error.join(','))
298}
299return await this.assetService.createAssetWithUpload(
300{ ownerId: userId, ...dtoAsset },
301file
302)
303}
304}
305
306/**
307* Get player's created assets
308* TODO this needs to be paginated
309*/
310@Get('me')
311@FirebaseTokenAuthGuard()
312@ApiOkResponse({ type: [AssetApiResponse] })
313@ApiQuery({ required: false })
314public async getAssetsForMe(
315@UserToken('user_id') userId: UserId,
316@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
317) {
318return await this.assetService.findAllAssetsForUserIncludingPrivate(
319userId,
320searchAssetDto,
321undefined,
322true
323)
324}
325/**
326* Same as above but no populate
327* TODO this needs to be paginated
328*/
329@Get('me-v2')
330@FirebaseTokenAuthGuard()
331@ApiOkResponse({ type: [AssetApiResponse] })
332@ApiQuery({ required: false })
333public async getAssetsForMeV2(
334@UserToken('user_id') userId: UserId,
335@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
336) {
337return await this.assetService.findAllAssetsForUserIncludingPrivate(
338userId,
339searchAssetDto,
340undefined,
341false
342)
343}
344
345/**
346* Get all player's accessible assets. Accessible assets are assets that are public, or assets that are private but owned by the user.
347*/
348@Get('my-library')
349@FirebaseTokenAuthGuard()
350@UsePipes(new ValidationPipe({ transform: true }))
351@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
352@ApiQuery({ required: false })
353public async getAllAccessibleAssetsOfUser(
354@UserToken('user_id') userId: UserId,
355@Query() searchAssetDto: PaginatedSearchAssetDtoV2
356): Promise<AssetFullDataPaginatedResponse> {
357return await this.assetService.findAllAccessibleAssetsOfUser(
358userId,
359searchAssetDto,
360true
361)
362}
363
364/**
365* @description Same as above but no populate
366* @date 2023-07-12 23:45
367*/
368@Get('my-library-v2')
369@FirebaseTokenAuthGuard()
370@UsePipes(new ValidationPipe({ transform: true }))
371@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
372@ApiQuery({ required: false })
373public async getAllAccessibleAssetsOfUserV2(
374@UserToken('user_id') userId: UserId,
375@Query() searchAssetDto: PaginatedSearchAssetDtoV2
376): Promise<AssetFullDataPaginatedResponse> {
377return await this.assetService.findAllAccessibleAssetsOfUser(
378userId,
379searchAssetDto,
380false
381)
382}
383
384@Get('recent')
385@FirebaseTokenAuthGuard()
386@UsePipes(new ValidationPipe({ transform: true }))
387@ApiOkResponse({ type: [AssetApiResponse] })
388public async getRecentAssetsForUser(
389@UserToken('user_id') userId: UserId,
390@Query('limit', ParseIntPipe) @Optional() limit?: number,
391@Query()
392includeSoftDeletedAssetDto?: IncludeSoftDeletedAssetDto
393) {
394const assets = await this.assetService.findRecentAssetsOfUserWithRolesCheck(
395userId,
396includeSoftDeletedAssetDto.includeSoftDeleted,
397limit,
398true
399)
400return assets
401}
402
403@Get('recent-v2')
404@FirebaseTokenAuthGuard()
405@UsePipes(new ValidationPipe({ transform: true }))
406@ApiOkResponse({ type: [AssetApiResponse] })
407public async getRecentAssetsForUserV2(
408@UserToken('user_id') userId: UserId,
409@Query('limit', ParseIntPipe) @Optional() limit?: number,
410@Query()
411includeSoftDeletedAssetDto?: IncludeSoftDeletedAssetDto
412) {
413const assets = await this.assetService.findRecentAssetsOfUserWithRolesCheck(
414userId,
415includeSoftDeletedAssetDto.includeSoftDeleted,
416limit,
417false
418)
419return assets
420}
421
422@Get('my-assets')
423@FirebaseTokenAuthGuard()
424@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
425@ApiQuery({ required: false })
426public async getPaginatedMyAssets(
427@UserToken('user_id') userId: UserId,
428@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
429): Promise<AssetFullDataPaginatedResponse> {
430return await this.assetService.findPaginatedMyAssetsWithRolesCheck(
431userId,
432searchAssetDto,
433true
434)
435}
436
437/**
438* @description Same as above but no populate
439* @date 2023-07-12 23:43
440*/
441@Get('my-assets-v2')
442@FirebaseTokenAuthGuard()
443@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
444@ApiQuery({ required: false })
445public async getPaginatedMyAssetsV2(
446@UserToken('user_id') userId: UserId,
447@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
448): Promise<AssetFullDataPaginatedResponse> {
449return await this.assetService.findPaginatedMyAssetsWithRolesCheck(
450userId,
451searchAssetDto,
452false
453)
454}
455
456@Get('mirror-assets')
457@FirebaseTokenAuthGuard()
458@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
459@ApiQuery({ required: false })
460public async getPaginatedMirrorAssets(
461@UserToken('user_id') userId: UserId,
462@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
463): Promise<AssetFullDataPaginatedResponse> {
464return await this.assetService.findPaginatedMirrorAssetsWithRolesCheck(
465userId,
466searchAssetDto,
467this.assetService.standardPopulateFields
468)
469}
470
471/**
472* @description Same as above but no populate
473* @date 2023-07-12 23:41
474*/
475@Get('mirror-assets-v2')
476@UseGuards(DomainOrAuthUserGuard)
477@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
478@ApiQuery({ required: false })
479public async getPaginatedMirrorAssetsV2(
480@UserToken('user_id') userId: UserId,
481@Query() searchAssetDto?: PaginatedSearchAssetDtoV2
482): Promise<AssetFullDataPaginatedResponse> {
483// parse through populate fields
484const populateFields: PopulateField[] =
485getPopulateFieldsFromPaginatedSearchAssetDto(searchAssetDto)
486
487return await this.assetService.findPaginatedMirrorAssetsWithRolesCheck(
488userId,
489searchAssetDto,
490populateFields
491)
492}
493
494@Get('user/:targetUserId')
495@FirebaseTokenAuthGuard()
496@ApiOkResponse({ type: [AssetApiResponse] })
497@ApiParam({ name: 'targetUserId', type: 'string', required: true })
498public async findAllForUser(
499@UserToken('user_id') requestingUserId: UserId,
500@Param('targetUserId') targetUserId: UserId,
501@Query() searchAssetDto?: SearchAssetDto
502) {
503// Validate that it's a Mongo ObjectId
504if (!Types.ObjectId.isValid(targetUserId)) {
505throw new BadRequestException('ID is not a valid Mongo ObjectID')
506}
507return await this.assetService.findAllPublicAssetsForUserWithRolesCheck(
508requestingUserId,
509targetUserId
510)
511}
512
513@Get('usage/:assetId')
514@FirebaseTokenAuthGuard()
515@ApiOkResponse({ type: AssetUsageApiResponse })
516@ApiParam({ name: 'assetId', type: 'string', required: true })
517public async findOneAssetUsage(
518@UserToken('user_id') userId: UserId,
519@Param('assetId') assetId: AssetId
520) {
521// Validate that it's a Mongo ObjectId
522if (!Types.ObjectId.isValid(assetId)) {
523throw new BadRequestException('ID is not a valid Mongo ObjectID')
524}
525return await this.assetService.findAssetUsageWithRolesCheck(userId, assetId)
526}
527
528@Get('tag')
529@ApiOkResponse({ type: AssetFullDataPaginatedResponse })
530@UseGuards(DomainOrAuthUserGuard)
531public async getAssetsByTag(
532@Query() searchDto: PaginatedSearchAssetDtoV2,
533@UserToken('user_id') userId: UserId
534) {
535return await this.assetService.getAssetsByTag(searchDto, userId)
536}
537
538@Post('tag')
539public async addTagToAssetsWithRoleChecks(
540@UserToken('user_id') userId: UserId,
541@Body() addTagToAssetDto: AddTagToAssetDto
542) {
543return await this.assetService.addTagToAssetsWithRoleChecks(
544userId,
545addTagToAssetDto
546)
547}
548
549@Patch('tag')
550@FirebaseTokenAuthGuard()
551public async updateAssetTagsByTypeWithRoleChecks(
552@UserToken('user_id') userId: UserId,
553@Body() updateAssetTagsDto: UpdateAssetTagsDto
554) {
555return await this.assetService.updateAssetTagsByTypeWithRoleChecks(
556userId,
557updateAssetTagsDto.assetId,
558updateAssetTagsDto.tagType,
559updateAssetTagsDto.tags
560)
561}
562
563@Delete('tag/:assetId/:tagType/:tagName')
564@FirebaseTokenAuthGuard()
565@ApiParam({ name: 'assetId', type: 'string', required: true })
566@ApiParam({ name: 'tagType', enum: TAG_TYPES, required: true })
567@ApiParam({ name: 'tagName', type: 'string', required: true })
568public async deleteTagFromAssetWithRoleChecks(
569@UserToken('user_id') userId: UserId,
570@Param('assetId') assetId: AssetId,
571@Param('tagType') tagType: TAG_TYPES,
572@Param('tagName') tagName: string
573) {
574return await this.assetService.deleteTagFromAssetWithRoleChecks(
575userId,
576assetId,
577tagName,
578tagType
579)
580}
581
582@Get(':id')
583@UseGuards(DomainOrAuthUserGuard)
584@ApiOkResponse({ type: AssetApiResponse })
585@ApiParam({ name: 'id', type: 'string', required: true })
586public async findOne(
587@UserToken('user_id') userId: UserId,
588@Param('id') assetId: AssetId
589) {
590// Validate that it's a Mongo ObjectId
591if (!Types.ObjectId.isValid(assetId)) {
592throw new BadRequestException('ID is not a valid Mongo ObjectID')
593}
594return await this.assetService.findOneWithRolesCheck(userId, assetId)
595}
596
597@Patch(':id')
598@FirebaseTokenAuthGuard()
599@ApiOkResponse({ type: AssetApiResponse })
600@ApiParam({ name: 'id', type: 'string', required: true })
601public async update(
602@UserToken('user_id') userId: UserId,
603@Param('id') assetId: AssetId,
604@Body() updateAssetDto: UpdateAssetDto
605) {
606// Validate that it's a Mongo ObjectId
607if (!Types.ObjectId.isValid(assetId)) {
608throw new BadRequestException('ID is not a valid Mongo ObjectID')
609}
610return await this.assetService.updateOneWithRolesCheck(
611userId,
612assetId,
613updateAssetDto
614)
615}
616
617@Delete(':id')
618@FirebaseTokenAuthGuard()
619@ApiParam({ name: 'id', type: 'string', required: true })
620@ApiOkResponse({ type: AssetApiResponse })
621public async remove(
622@UserToken('user_id') userId: UserId,
623@Param('id') assetId: AssetId
624) {
625return await this.assetService.removeOneWithRolesCheck(userId, assetId)
626}
627
628/**
629* @description This endpoint is used to undo soft delete of an asset.
630* (Remove isSoftDeleted and softDeletedAt fields from the asset document)
631*
632* @date 2023-11-23
633*/
634@Post('undo-soft-delete/:assetId')
635@FirebaseTokenAuthGuard()
636@ApiParam({ name: 'assetId', type: 'string', required: true })
637@ApiOkResponse({ type: String })
638public async undoAssetSoftDelete(
639@UserToken('user_id') userId: UserId,
640@Param('assetId') assetId: AssetId
641) {
642return await this.assetService.undoAssetSoftDelete(userId, assetId)
643}
644
645///////////////////////////////////////
646/// File Uploading Below //////////////
647///////////////////////////////////////
648
649@Post('/:assetId/upload')
650@FirebaseTokenAuthGuard()
651@ApiParam({ name: 'assetId', type: 'string', required: true })
652@ApiConsumes('multipart/form-data')
653@ApiBody({ schema: { type: 'file' } })
654@ApiCreatedResponse({ type: FileUploadApiResponse })
655@UseInterceptors(
656FileInterceptor('file', {
657limits: { fieldSize: 20 * 1024 * 1024, fileSize: 20 * 1024 * 1024 } // 20MB limit. Arbitrary. I wanted to reference a class property of this.fileSizeBytes, but not sure if that's accessible in a decorator
658})
659)
660public async upload(
661@Param('assetId') assetId: AssetId,
662@UserToken('user_id') userId: UserId,
663@UploadedFile() file: Express.Multer.File
664) {
665// Validate that it's a Mongo ObjectId
666if (!Types.ObjectId.isValid(assetId)) {
667throw new BadRequestException('ID is not a valid Mongo ObjectID')
668}
669
670const { relativePath: currentFile } =
671await this.assetService.uploadAssetFileWithRolesCheck({
672assetId,
673userId,
674file
675})
676
677return await this.assetService.updateOneWithRolesCheck(userId, assetId, {
678currentFile
679})
680}
681
682@Post('/:id/upload/public')
683@FirebaseTokenAuthGuard()
684@ApiParam({ name: 'id', type: 'string', required: true })
685@ApiConsumes('multipart/form-data')
686@ApiBody({ schema: { type: 'file' } })
687@ApiCreatedResponse({ type: FileUploadPublicApiResponse })
688@UseInterceptors(
689FileInterceptor('file', {
690limits: { fieldSize: 20 * 1024 * 1024, fileSize: 20 * 1024 * 1024 } // 20MB limit. Arbitrary. I wanted to reference a class property of this.fileSizeBytes, but not sure if that's accessible in a decorator
691})
692)
693public async uploadPublic(
694@Param('id') assetId: AssetId,
695@UserToken('user_id') userId: UserId,
696@UploadedFile() file: Express.Multer.File
697) {
698// Validate that it's a Mongo ObjectId
699if (!Types.ObjectId.isValid(assetId)) {
700throw new BadRequestException('ID is not a valid Mongo ObjectID')
701}
702const { publicUrl: currentFile } =
703await this.assetService.uploadAssetFilePublicWithRolesCheck({
704assetId,
705userId,
706file
707})
708
709return await this.assetService.updateOneWithRolesCheck(userId, assetId, {
710currentFile
711})
712}
713
714@Post('/:id/upload/thumbnail')
715@FirebaseTokenAuthGuard()
716@ApiParam({ name: 'id', type: 'string', required: true })
717@ApiConsumes('multipart/form-data')
718@ApiBody({ schema: { type: 'file' } })
719@ApiCreatedResponse({ type: FileUploadPublicApiResponse })
720@UseInterceptors(
721FileInterceptor('file', {
722limits: { fieldSize: 20 * 1024 * 1024, fileSize: 20 * 1024 * 1024 } // 20MB limit. Arbitrary. I wanted to reference a class property of this.fileSizeBytes, but not sure if that's accessible in a decorator
723})
724)
725public async uploadThumbnail(
726@Param('id') assetId: AssetId,
727@UserToken('user_id') userId: UserId,
728@UploadedFile() file: Express.Multer.File
729) {
730// Validate that it's a Mongo ObjectId
731if (!Types.ObjectId.isValid(assetId)) {
732throw new BadRequestException('ID is not a valid Mongo ObjectID')
733}
734const { publicUrl: thumbnail } =
735await this.assetService.uploadAssetThumbnailWithRolesCheck({
736assetId,
737userId,
738file
739})
740
741return await this.assetService.updateOneWithRolesCheck(userId, assetId, {
742thumbnail
743})
744}
745
746@Get('by/start-item')
747@FirebaseTokenAuthGuard()
748public async getAsset(
749@UserToken('user_id') userId: UserId,
750@Query() queryParams: PaginatedSearchAssetDtoV2
751) {
752return await this.assetService.getPaginatedQueryResponseByStartItemWithRolesCheck(
753userId,
754queryParams
755)
756}
757
758@Post('/:assetId/purchase-option')
759@FirebaseTokenAuthGuard()
760@ApiBody({
761type: AddAssetPurchaseOptionDto
762})
763@ApiParam({ name: 'assetId', type: 'string', required: true })
764public async addAssetPurchaseOption(
765@Param('assetId') assetId: AssetId,
766@UserToken('user_id') userId: UserId,
767@Body() data: AddAssetPurchaseOptionDto
768) {
769return await this.assetService.addAssetPurchaseOption(userId, assetId, data)
770}
771
772@Delete('/:assetId/purchase-option/:purchaseOptionId')
773@FirebaseTokenAuthGuard()
774@ApiBody({
775type: AddAssetPurchaseOptionDto
776})
777@ApiParam({ name: 'assetId', type: 'string', required: true })
778@ApiParam({ name: 'purchaseOptionId', type: 'string', required: true })
779public async deleteAssetPurchaseOption(
780@Param('assetId') assetId: AssetId,
781@Param('purchaseOptionId') purchaseOptionId: string,
782@UserToken('user_id') userId: UserId
783) {
784return await this.assetService.deleteAssetPurchaseOption(
785userId,
786assetId,
787purchaseOptionId
788)
789}
790
791@Get('check-if-asset-copied/:assetId')
792@FirebaseTokenAuthGuard()
793@ApiParam({ name: 'assetId', type: 'string', required: true })
794public async checkIfAssetCopied(
795@UserToken('user_id') userId: UserId,
796@Param('assetId') assetId: AssetId
797) {
798return await this.assetService.checkIfAssetCopiedByUser(assetId, userId)
799}
800
801@Post('copy-free-asset/:assetId')
802@ApiParam({ name: 'assetId', type: 'string', required: true })
803@FirebaseTokenAuthGuard()
804public async copyFreeAsset(
805@UserToken('user_id') userId: UserId,
806@Param('assetId') assetId: AssetId
807) {
808return await this.assetService.copyFreeAssetToNewUserWithRolesCheck(
809userId,
810assetId
811)
812}
813
814@Get('download/:assetId')
815@ApiParam({ name: 'assetId', type: 'string', required: true })
816@FirebaseTokenAuthGuard()
817public async downloadAsset(
818@UserToken('user_id') userId: UserId,
819@Param('assetId') assetId: AssetId,
820@Res() res: Response
821) {
822return await this.assetService.downloadAssetFileWithRoleChecks(
823userId,
824assetId,
825res
826)
827}
828
829@Get('/space/:spaceId')
830@FirebaseTokenAuthGuard()
831public async getAllAssetsBySpaceIdWithRolesCheck(
832@UserToken('user_id') userId: UserId,
833@Param('spaceId') spaceId: string
834) {
835return await this.assetService.getAllAssetsBySpaceIdWithRolesCheck(
836spaceId,
837userId
838)
839}
840
841@Patch('pack/add-asset/:packId/:assetId')
842@FirebaseTokenAuthGuard()
843@ApiParam({ name: 'assetId', type: 'string', required: true })
844public async addAssetToPackWithRolesCheck(
845@UserToken('user_id') userId: UserId,
846@Param('packId') packId: string,
847@Param('assetId') assetId: string
848) {
849return await this.assetService.addAssetToPackWithRolesCheck(
850packId,
851assetId,
852userId
853)
854}
855
856@Delete('pack/remove-asset/:packId/:assetId')
857@FirebaseTokenAuthGuard()
858@ApiParam({ name: 'assetId', type: 'string', required: true })
859public async deleteAssetFromPackWithRolesCheck(
860@UserToken('user_id') userId: UserId,
861@Param('packId') packId: string,
862@Param('assetId') assetId: string
863) {
864return await this.assetService.deleteAssetFromPackWithRolesCheck(
865packId,
866assetId,
867userId
868)
869}
870}
871