diff --git a/services/content-publishing/README.md b/services/content-publishing/README.md index e92a46da..b45229bf 100644 --- a/services/content-publishing/README.md +++ b/services/content-publishing/README.md @@ -167,27 +167,16 @@ Use the provided [env.template](./env.template) file to create an initial enviro ```sh cp env.template .env - cp env.template .env.docker.dev ``` 2. Configure the environment variable values according to your environment. -### Setup - -Clone this repository to your desired folder: - -Example commands: - -```sh - git clone git@github.com:AmplicaLabs/content-publishing-service.git - cd content-publishing-service -``` - ### Install Install NPM Dependencies: ```sh + cd services/content-publishing npm install ``` diff --git a/services/content-publishing/apps/api/src/api.service.ts b/services/content-publishing/apps/api/src/api.service.ts index 46868078..b1aae1e3 100644 --- a/services/content-publishing/apps/api/src/api.service.ts +++ b/services/content-publishing/apps/api/src/api.service.ts @@ -6,15 +6,8 @@ import { BulkJobOptions } from 'bullmq/dist/esm/interfaces'; import { InjectRedis } from '@songkeys/nestjs-redis'; import Redis from 'ioredis'; import { HttpErrorByCode } from '@nestjs/common/utils/http-error-by-code.util'; -import { - AnnouncementTypeDto, - RequestTypeDto, - AnnouncementResponseDto, - AssetIncludedRequestDto, - isImage, - UploadResponseDto, -} from '#libs/dtos'; -import { IRequestJob, IAssetMetadata, IAssetJob } from '#libs/interfaces'; +import { AnnouncementTypeDto, RequestTypeDto, AnnouncementResponseDto, AssetIncludedRequestDto, isImage, UploadResponseDto, AttachmentType } from '#libs/dtos'; +import { IRequestJob, IAssetMetadata, IAssetJob, IAssetTypeInfo } from '#libs/interfaces'; import { REQUEST_QUEUE_NAME, ASSET_QUEUE_NAME } from '#libs/queues/queue.constants'; import { calculateIpfsCID } from '#libs/utils/ipfs'; import { getAssetMetadataKey, getAssetDataKey, STORAGE_EXPIRE_UPPER_LIMIT_SECONDS } from '#libs/utils/redis'; @@ -35,7 +28,7 @@ export class ApiService { announcementType: AnnouncementTypeDto, dsnpUserId: string, content: RequestTypeDto, - assetToMimeType?: Map, + assetToMimeType?: IRequestJob['assetToMimeType'], ): Promise { const data = { content, @@ -60,12 +53,10 @@ export class ApiService { }; } - async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise | undefined> { + async validateAssetsAndFetchMetadata(content: AssetIncludedRequestDto): Promise { const checkingList: { onlyImage: boolean; referenceId: string }[] = []; if (content.profile) { - content.profile.icon?.forEach((reference) => - checkingList.push({ onlyImage: true, referenceId: reference.referenceId }), - ); + content.profile.icon?.forEach((reference) => checkingList.push({ onlyImage: true, referenceId: reference.referenceId })); } else if (content.content) { content.content.assets?.forEach((asset) => asset.references?.forEach((reference) => @@ -77,19 +68,15 @@ export class ApiService { ); } - const redisResults = await Promise.all( - checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId))), - ); + const redisResults = await Promise.all(checkingList.map((obj) => this.redis.get(getAssetMetadataKey(obj.referenceId)))); const errors: string[] = []; const map = new Map(); redisResults.forEach((res, index) => { if (res === null) { - errors.push( - `${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`, - ); + errors.push(`${content.profile ? 'profile.icon' : 'content.assets'}.referenceId ${checkingList[index].referenceId} does not exist!`); } else { const metadata: IAssetMetadata = JSON.parse(res); - map[checkingList[index].referenceId] = metadata.mimeType; + map[checkingList[index].referenceId] = { mimeType: metadata.mimeType, attachmentType: metadata.type }; // checks if attached asset is an image if (checkingList[index].onlyImage && !isImage(metadata.mimeType)) { @@ -114,20 +101,29 @@ export class ApiService { const jobs: any[] = []; files.forEach((f, index) => { // adding data and metadata to the transaction - dataTransaction = dataTransaction.setex( - getAssetDataKey(references[index]), - STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, - f.buffer, - ); - metadataTransaction = metadataTransaction.setex( - getAssetMetadataKey(references[index]), - STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, - JSON.stringify({ - ipfsCid: references[index], - mimeType: f.mimetype, - createdOn: Date.now(), - } as IAssetMetadata), - ); + dataTransaction = dataTransaction.setex(getAssetDataKey(references[index]), STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, f.buffer); + const type = ((m) => { + switch (m) { + case 'image': + return AttachmentType.IMAGE; + case 'audio': + return AttachmentType.AUDIO; + case 'video': + return AttachmentType.VIDEO; + default: + throw new Error('Invalid MIME type'); + } + })(f.mimetype.split('/')[0]); + + const assetCache: IAssetMetadata = { + ipfsCid: references[index], + mimeType: f.mimetype, + createdOn: Date.now(), + type: type, + }; + + metadataTransaction = metadataTransaction.setex(getAssetMetadataKey(references[index]), STORAGE_EXPIRE_UPPER_LIMIT_SECONDS, JSON.stringify(assetCache)); + // adding asset job to the jobs jobs.push({ name: `Asset Job - ${references[index]}`, diff --git a/services/content-publishing/apps/api/src/metadata.ts b/services/content-publishing/apps/api/src/metadata.ts index bf1f6e63..82bb0bd6 100644 --- a/services/content-publishing/apps/api/src/metadata.ts +++ b/services/content-publishing/apps/api/src/metadata.ts @@ -1,8 +1,93 @@ /* eslint-disable */ export default async () => { - const t = { - ["../../../libs/common/src/dtos/activity.dto"]: await import("../../../libs/common/src/dtos/activity.dto"), - ["../../../libs/common/src/dtos/announcement.dto"]: await import("../../../libs/common/src/dtos/announcement.dto") - }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/common/src/dtos/common.dto"), { "DsnpUserIdParam": { userDsnpId: { required: true, type: () => String } }, "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/common/src/dtos/activity.dto"), { "LocationDto": { name: { required: true, type: () => String, minLength: 1 }, accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, altitude: { required: false, type: () => Number }, latitude: { required: false, type: () => Number }, longitude: { required: false, type: () => Number }, radius: { required: false, type: () => Number, minimum: 0 }, units: { required: false, enum: t["../../../libs/common/src/dtos/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number, minimum: 1 }, width: { required: false, type: () => Number, minimum: 1 }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String } }, "AssetDto": { type: { required: true, enum: t["../../../libs/common/src/dtos/activity.dto"].AttachmentTypeDto }, references: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, name: { required: false, type: () => String, minLength: 1 }, href: { required: false, type: () => String, minLength: 1 } }, "BaseActivityDto": { name: { required: false, type: () => String }, tag: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/common/src/dtos/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/common/src/dtos/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/common/src/dtos/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/common/src/dtos/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, inReplyTo: { required: true, type: () => String } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/common/src/dtos/activity.dto"].ProfileActivityDto } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }]] } }; -}; \ No newline at end of file + const t = { + ['../../../libs/common/src/dtos/activity.dto']: await import('../../../libs/common/src/dtos/activity.dto'), + ['../../../libs/common/src/dtos/announcement.dto']: await import('../../../libs/common/src/dtos/announcement.dto'), + }; + return { + '@nestjs/swagger': { + models: [ + [ + import('../../../libs/common/src/dtos/common.dto'), + { + DsnpUserIdParam: { userDsnpId: { required: true, type: () => String } }, + AnnouncementResponseDto: { referenceId: { required: true, type: () => String } }, + UploadResponseDto: { assetIds: { required: true, type: () => [String] } }, + FilesUploadDto: { files: { required: true, type: () => [Object] } }, + }, + ], + [ + import('../../../libs/common/src/dtos/activity.dto'), + { + LocationDto: { + name: { required: true, type: () => String, minLength: 1 }, + accuracy: { required: false, type: () => Number, minimum: 0, maximum: 100 }, + altitude: { required: false, type: () => Number }, + latitude: { required: false, type: () => Number }, + longitude: { required: false, type: () => Number }, + radius: { required: false, type: () => Number, minimum: 0 }, + units: { required: false, enum: t['../../../libs/common/src/dtos/activity.dto'].UnitTypeDto }, + }, + AssetReferenceDto: { + referenceId: { required: true, type: () => String, minLength: 1 }, + height: { required: false, type: () => Number, minimum: 1 }, + width: { required: false, type: () => Number, minimum: 1 }, + duration: { required: false, type: () => String, pattern: 'DURATION_REGEX' }, + }, + TagDto: { + type: { required: true, enum: t['../../../libs/common/src/dtos/activity.dto'].TagTypeDto }, + name: { required: false, type: () => String, minLength: 1 }, + mentionedId: { required: false, type: () => String }, + }, + AssetDto: { + references: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + name: { required: false, type: () => String, minLength: 1 }, + href: { required: false, type: () => String, minLength: 1 }, + }, + BaseActivityDto: { + name: { required: false, type: () => String }, + tag: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].TagDto] }, + location: { required: false, type: () => t['../../../libs/common/src/dtos/activity.dto'].LocationDto }, + }, + NoteActivityDto: { + content: { required: true, type: () => String, minLength: 1 }, + published: { required: true, type: () => String }, + assets: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetDto] }, + }, + ProfileActivityDto: { + icon: { required: false, type: () => [t['../../../libs/common/src/dtos/activity.dto'].AssetReferenceDto] }, + summary: { required: false, type: () => String }, + published: { required: false, type: () => String }, + }, + }, + ], + [ + import('../../../libs/common/src/dtos/announcement.dto'), + { + BroadcastDto: { content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto } }, + ReplyDto: { + inReplyTo: { required: true, type: () => String }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + TombstoneDto: { + targetContentHash: { required: true, type: () => String }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + }, + UpdateDto: { + targetContentHash: { required: true, type: () => String }, + targetAnnouncementType: { required: true, enum: t['../../../libs/common/src/dtos/announcement.dto'].ModifiableAnnouncementTypeDto }, + content: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].NoteActivityDto }, + }, + ReactionDto: { + emoji: { required: true, type: () => String, minLength: 1, pattern: 'DSNP_EMOJI_REGEX' }, + apply: { required: true, type: () => Number, minimum: 0, maximum: 255 }, + inReplyTo: { required: true, type: () => String }, + }, + ProfileDto: { profile: { required: true, type: () => t['../../../libs/common/src/dtos/activity.dto'].ProfileActivityDto } }, + }, + ], + ], + controllers: [[import('./controllers/health.controller'), { HealthController: { healthz: {}, livez: {}, readyz: {} } }]], + }, + }; +}; diff --git a/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts b/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts index 296fcd0f..f7a4064b 100644 --- a/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts +++ b/services/content-publishing/apps/worker/src/request_processor/dsnp.announcement.processor.ts @@ -24,8 +24,9 @@ import { TombstoneDto, ModifiableAnnouncementTypeDto, TagTypeDto, - AttachmentTypeDto, + AttachmentType, AssetDto, + TagDto, } from '#libs/dtos'; import { IRequestJob, @@ -43,14 +44,7 @@ import { ProfileAnnouncement, createProfile, } from '#libs/interfaces'; -import { - BROADCAST_QUEUE_NAME, - REPLY_QUEUE_NAME, - REACTION_QUEUE_NAME, - UPDATE_QUEUE_NAME, - PROFILE_QUEUE_NAME, - TOMBSTONE_QUEUE_NAME, -} from '#libs/queues/queue.constants'; +import { BROADCAST_QUEUE_NAME, REPLY_QUEUE_NAME, REACTION_QUEUE_NAME, UPDATE_QUEUE_NAME, PROFILE_QUEUE_NAME, TOMBSTONE_QUEUE_NAME } from '#libs/queues/queue.constants'; import { calculateDsnpHash } from '#libs/utils/ipfs'; import { IpfsService } from '#libs/utils/ipfs.client'; @@ -132,16 +126,8 @@ export class DsnpAnnouncementProcessor { private async queueUpdate(data: IRequestJob) { const updateDto = data.content as UpdateDto; - const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType( - updateDto.targetAnnouncementType, - ); - const update = await this.processUpdate( - updateDto, - updateAnnouncementType, - updateDto.targetContentHash ?? '', - data.dsnpUserId, - data.assetToMimeType, - ); + const updateAnnouncementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(updateDto.targetAnnouncementType); + const update = await this.processUpdate(updateDto, updateAnnouncementType, updateDto.targetContentHash ?? '', data.dsnpUserId, data.assetToMimeType); await this.updateQueue.add(`Update Job - ${data.id}`, update, { jobId: data.id, removeOnFail: false, @@ -160,9 +146,7 @@ export class DsnpAnnouncementProcessor { private async queueTombstone(data: IRequestJob) { const tombStoneDto = data.content as TombstoneDto; - const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType( - tombStoneDto.targetAnnouncementType, - ); + const announcementType: AnnouncementType = await this.getAnnouncementTypeFromModifiableAnnouncementType(tombStoneDto.targetAnnouncementType); const tombstone = createTombstone(data.dsnpUserId, announcementType, tombStoneDto.targetContentHash ?? ''); await this.tombstoneQueue.add(`Tombstone Job - ${data.id}`, tombstone, { jobId: data.id, @@ -171,9 +155,7 @@ export class DsnpAnnouncementProcessor { }); } - private async getAnnouncementTypeFromModifiableAnnouncementType( - modifiableAnnouncementType: ModifiableAnnouncementTypeDto, - ): Promise { + private async getAnnouncementTypeFromModifiableAnnouncementType(modifiableAnnouncementType: ModifiableAnnouncementTypeDto): Promise { this.logger.debug(`Getting announcement type from modifiable announcement type`); switch (modifiableAnnouncementType) { case ModifiableAnnouncementTypeDto.BROADCAST: @@ -185,13 +167,10 @@ export class DsnpAnnouncementProcessor { } } - public async prepareNote(noteContent: any, assetToMimeType?: Map): Promise<[string, string, string]> { - this.logger.debug(`Preparing note`); + public async prepareNote(noteContent: BroadcastDto | ReplyDto | UpdateDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise<[string, string, string]> { + this.logger.debug(`Preparing note of type: ${typeof noteContent.content}`); const tags: ActivityContentTag[] = this.prepareTags(noteContent?.content.tag); - const attachments: ActivityContentAttachment[] = await this.prepareAttachments( - noteContent.content.assets, - assetToMimeType, - ); + const attachments: ActivityContentAttachment[] = await this.prepareAttachments(noteContent.content.assets, assetToMimeType); const note = createNote(noteContent.content.content ?? '', new Date(noteContent.content.published ?? ''), { name: noteContent.content.name, @@ -205,16 +184,22 @@ export class DsnpAnnouncementProcessor { return [cid, ipfsUrl, hash]; } - private prepareTags(tagData?: any[]): ActivityContentTag[] { + private prepareTags(tagData?: TagDto[]): ActivityContentTag[] { this.logger.debug(`Preparing tags`); const tags: ActivityContentTag[] = []; if (tagData) { tagData.forEach((tag) => { switch (tag.type) { case TagTypeDto.Hashtag: + if (!tag.name) { + throw new Error(`Tag name is required`); + } tags.push({ name: tag.name }); break; case TagTypeDto.Mention: + if (!tag.mentionedId) { + throw new Error(`Mentioned ID is required`); + } tags.push({ name: tag.name, type: 'Mention', @@ -229,33 +214,38 @@ export class DsnpAnnouncementProcessor { return tags; } - private async prepareAttachments( - assetData?: any[], - assetToMimeType?: Map, - ): Promise { + private async prepareAttachments(assetData?: AssetDto[], assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const attachments: ActivityContentAttachment[] = []; if (assetData) { const promises = assetData.map(async (asset) => { - switch (asset.type) { - case AttachmentTypeDto.LINK: - attachments.push(this.prepareLinkAttachment(asset)); - break; - case AttachmentTypeDto.IMAGE: - attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.VIDEO: - attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); - break; - case AttachmentTypeDto.AUDIO: - attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); - break; - default: - throw new Error(`Unsupported attachment type ${typeof asset.type}`); + if (asset.references) { + const assetPromises = asset.references.map(async (reference) => { + if (!assetToMimeType) { + throw new Error(`asset ${reference.referenceId} should have a mimeTypes`); + } + const { attachmentType } = assetToMimeType[reference.referenceId]; + switch (attachmentType) { + case AttachmentType.LINK: + attachments.push(this.prepareLinkAttachment(asset)); + break; + case AttachmentType.IMAGE: + attachments.push(await this.prepareImageAttachment(asset, assetToMimeType)); + break; + case AttachmentType.VIDEO: + attachments.push(await this.prepareVideoAttachment(asset, assetToMimeType)); + break; + case AttachmentType.AUDIO: + attachments.push(await this.prepareAudioAttachment(asset, assetToMimeType)); + break; + default: + throw new Error(`Unsupported attachment type ${attachmentType}`); + } + }); + await Promise.all(assetPromises); } }); await Promise.all(promises); } - return attachments; } @@ -268,10 +258,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareImageAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareImageAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const imageLinks: ActivityContentImageLink[] = []; if (asset.references) { const promises = asset.references.map(async (reference) => { @@ -301,10 +288,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareVideoAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareVideoAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const videoLinks: ActivityContentVideoLink[] = []; let duration: string | undefined = ''; @@ -338,10 +322,7 @@ export class DsnpAnnouncementProcessor { }; } - private async prepareAudioAttachment( - asset: AssetDto, - assetToMimeType?: Map, - ): Promise { + private async prepareAudioAttachment(asset: AssetDto, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const audioLinks: ActivityContentAudioLink[] = []; let duration = ''; if (asset.references) { @@ -373,21 +354,13 @@ export class DsnpAnnouncementProcessor { }; } - private async processBroadcast( - content: BroadcastDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processBroadcast(content: BroadcastDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing broadcast`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createBroadcast(dsnpUserId, ipfsUrl, hash); } - private async processReply( - content: ReplyDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processReply(content: ReplyDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing reply for ${content.inReplyTo}`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createReply(dsnpUserId, ipfsUrl, hash, content.inReplyTo); @@ -403,23 +376,16 @@ export class DsnpAnnouncementProcessor { targetAnnouncementType: AnnouncementType, targetContentHash: string, dsnpUserId: string, - assetToMimeType?: Map, + assetToMimeType?: IRequestJob['assetToMimeType'], ): Promise { this.logger.debug(`Processing update`); const [cid, ipfsUrl, hash] = await this.prepareNote(content, assetToMimeType); return createUpdate(dsnpUserId, ipfsUrl, hash, targetAnnouncementType, targetContentHash); } - private async processProfile( - content: ProfileDto, - dsnpUserId: string, - assetToMimeType?: Map, - ): Promise { + private async processProfile(content: ProfileDto, dsnpUserId: string, assetToMimeType?: IRequestJob['assetToMimeType']): Promise { this.logger.debug(`Processing profile`); - const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments( - content.profile.icon ?? [], - assetToMimeType, - ); + const attachments: ActivityContentImageLink[] = await this.prepareProfileIconAttachments(content.profile.icon ?? [], assetToMimeType); const profileActivity: ActivityContentProfile = { '@context': 'https://www.w3.org/ns/activitystreams', @@ -436,10 +402,7 @@ export class DsnpAnnouncementProcessor { return createProfile(dsnpUserId, this.formIpfsUrl(cid), hash); } - private async prepareProfileIconAttachments( - icons: any[], - assetToMimeType?: Map, - ): Promise { + private async prepareProfileIconAttachments(icons: any[], assetToMimeType?: IRequestJob['assetToMimeType']): Promise { const attachments: ActivityContentImageLink[] = []; const promises = icons.map(async (icon) => { diff --git a/services/content-publishing/libs/common/src/dtos/activity.dto.ts b/services/content-publishing/libs/common/src/dtos/activity.dto.ts index 053cb24c..668b6df2 100644 --- a/services/content-publishing/libs/common/src/dtos/activity.dto.ts +++ b/services/content-publishing/libs/common/src/dtos/activity.dto.ts @@ -43,7 +43,7 @@ export enum TagTypeDto { } // eslint-disable-next-line no-shadow -export enum AttachmentTypeDto { +export enum AttachmentType { LINK = 'link', IMAGE = 'image', AUDIO = 'audio', @@ -121,10 +121,7 @@ export class TagDto { } export class AssetDto { - @IsEnum(AttachmentTypeDto) - type: AttachmentTypeDto; - - @ValidateIf((o) => o.type !== AttachmentTypeDto.LINK) + @ValidateIf((o) => o.type !== AttachmentType.LINK) @ValidateNested({ each: true }) @IsArray() @ArrayNotEmpty() @@ -137,7 +134,7 @@ export class AssetDto { @MinLength(1) name?: string; - @ValidateIf((o) => o.type === AttachmentTypeDto.LINK) + @ValidateIf((o) => o.type === AttachmentType.LINK) @IsString() @MinLength(1) @IsUrl({ protocols: ['http', 'https'] }) diff --git a/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts b/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts index 3da2dc91..cf38549b 100644 --- a/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts +++ b/services/content-publishing/libs/common/src/interfaces/asset-job.interface.ts @@ -1,3 +1,5 @@ +import { AttachmentType } from '#libs/dtos'; + export interface IAssetJob { ipfsCid: string; mimeType: string; @@ -9,4 +11,5 @@ export interface IAssetMetadata { ipfsCid: string; mimeType: string; createdOn: number; + type: AttachmentType; } diff --git a/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts b/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts index c2822a92..98577b7d 100644 --- a/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts +++ b/services/content-publishing/libs/common/src/interfaces/request-job.interface.ts @@ -1,10 +1,13 @@ -import { AnnouncementTypeDto, RequestTypeDto } from '#libs/dtos'; - +import { AnnouncementTypeDto, AttachmentType, RequestTypeDto } from '#libs/dtos'; +export interface IAssetTypeInfo { + mimeType: string; + attachmentType: AttachmentType; +} export interface IRequestJob { id: string; announcementType: AnnouncementTypeDto; dsnpUserId: string; - assetToMimeType?: Map; + assetToMimeType?: Map; content?: RequestTypeDto; dependencyAttempt: number; } diff --git a/services/content-publishing/swagger.json b/services/content-publishing/swagger.json index f6aa53fc..99100aec 100644 --- a/services/content-publishing/swagger.json +++ b/services/content-publishing/swagger.json @@ -403,15 +403,6 @@ "AssetDto": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "link", - "image", - "audio", - "video" - ] - }, "references": { "type": "array", "items": { @@ -426,10 +417,7 @@ "type": "string", "minLength": 1 } - }, - "required": [ - "type" - ] + } }, "TagDto": { "type": "object",