diff --git a/apps/content-publishing-api/k6-test/script.k6.js b/apps/content-publishing-api/k6-test/script.k6.js index c2336633..3267a0b0 100644 --- a/apps/content-publishing-api/k6-test/script.k6.js +++ b/apps/content-publishing-api/k6-test/script.k6.js @@ -37,13 +37,13 @@ export const options = { }; export default function () { - group('/v1/content/{userDsnpId}', () => { - let userDsnpId = '1'; + group('/v1/content/{msaId}', () => { + let msaId = '1'; // Request No. 1: ApiController_update with no assets { - let url = BASE_URL + `/v1/content/${userDsnpId}`; + let url = BASE_URL + `/v1/content/${msaId}`; const body = { - targetContentHash: '0x7653423447AF', + targetContentHash: 'bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna', targetAnnouncementType: 'broadcast', content: validContentNoUploadedAssets, }; @@ -56,9 +56,9 @@ export default function () { } // Request No. 2: ApiController_update with assets { - let url = BASE_URL + `/v1/content/${userDsnpId}`; + let url = BASE_URL + `/v1/content/${msaId}`; const body = { - targetContentHash: '0x7653423447AF', + targetContentHash: 'bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna', targetAnnouncementType: 'broadcast', content: createContentWithAsset(BASE_URL), }; @@ -72,9 +72,9 @@ export default function () { // Request No. 3: ApiController_delete { - let url = BASE_URL + `/v1/content/${userDsnpId}`; + let url = BASE_URL + `/v1/content/${msaId}`; let body = { - targetContentHash: '0x7653423447AF', + targetContentHash: 'bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna', targetAnnouncementType: 'broadcast', }; let params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; @@ -99,12 +99,12 @@ export default function () { } }); - group('/v1/profile/{userDsnpId}', () => { - let userDsnpId = '1'; // specify value as there is no example value for this parameter in OpenAPI spec + group('/v1/profile/{msaId}', () => { + let msaId = '1'; // specify value as there is no example value for this parameter in OpenAPI spec // Request No. 1: ApiController_profile with no assets { - let url = BASE_URL + `/v1/profile/${userDsnpId}`; + let url = BASE_URL + `/v1/profile/${msaId}`; let body = { profile: validProfileNoUploadedAssets }; let params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; let request = http.put(url, JSON.stringify(body), params); @@ -115,7 +115,7 @@ export default function () { } // Request No. 2: ApiController_profile with asset { - let url = BASE_URL + `/v1/profile/${userDsnpId}`; + let url = BASE_URL + `/v1/profile/${msaId}`; const referenceId = getReferenceId(BASE_URL); let profile = Object.assign({}, validProfileNoUploadedAssets, { icon: [ @@ -136,12 +136,12 @@ export default function () { } }); - group('/v1/content/{userDsnpId}/broadcast', () => { - let userDsnpId = '1'; + group('/v1/content/{msaId}/broadcast', () => { + let msaId = '1'; // Request No. 1: ApiController_broadcast no assets { - let url = BASE_URL + `/v1/content/${userDsnpId}/broadcast`; + let url = BASE_URL + `/v1/content/${msaId}/broadcast`; const body = { content: validContentNoUploadedAssets, }; @@ -154,7 +154,7 @@ export default function () { } // Request No. 2: ApiController_broadcast with assets { - let url = BASE_URL + `/v1/content/${userDsnpId}/broadcast`; + let url = BASE_URL + `/v1/content/${msaId}/broadcast`; const body = { content: createContentWithAsset(BASE_URL), }; @@ -167,12 +167,12 @@ export default function () { } }); - group('/v1/content/{userDsnpId}/reaction', () => { - let userDsnpId = '1'; + group('/v1/content/{msaId}/reaction', () => { + let msaId = '1'; // Request No. 1: ApiController_reaction { - let url = BASE_URL + `/v1/content/${userDsnpId}/reaction`; + let url = BASE_URL + `/v1/content/${msaId}/reaction`; let body = validReaction; let params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; let request = http.post(url, JSON.stringify(body), params); @@ -183,12 +183,12 @@ export default function () { } }); - group('/v1/content/{userDsnpId}/reply', () => { - let userDsnpId = '1'; + group('/v1/content/{msaId}/reply', () => { + let msaId = '1'; // Request No. 1: ApiController_reply no assets { - let url = BASE_URL + `/v1/content/${userDsnpId}/reply`; + let url = BASE_URL + `/v1/content/${msaId}/reply`; let body = validReplyNoUploadedAssets; let params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; let request = http.post(url, JSON.stringify(body), params); @@ -199,7 +199,7 @@ export default function () { } // Request No. 2: ApiController_reply with assets { - let url = BASE_URL + `/v1/content/${userDsnpId}/reply`; + let url = BASE_URL + `/v1/content/${msaId}/reply`; let body = Object.assign({}, validReplyNoUploadedAssets, { content: createContentWithAsset(BASE_URL) }); let params = { headers: { 'Content-Type': 'application/json', Accept: 'application/json' } }; let request = http.post(url, JSON.stringify(body), params); diff --git a/apps/content-publishing-api/src/api.config.spec.ts b/apps/content-publishing-api/src/api.config.spec.ts index 6b774395..9d86bacc 100644 --- a/apps/content-publishing-api/src/api.config.spec.ts +++ b/apps/content-publishing-api/src/api.config.spec.ts @@ -12,6 +12,7 @@ describe('Content Publishing API Config', () => { API_PORT: undefined, // API_TIMEOUT_MS: undefined, FILE_UPLOAD_MAX_SIZE_IN_BYTES: undefined, + FILE_UPLOAD_COUNT_LIMIT: undefined, }; beforeAll(() => { @@ -28,8 +29,13 @@ describe('Content Publishing API Config', () => { it('missing file upload limit should fail', async () => validateMissing(ALL_ENV, 'FILE_UPLOAD_MAX_SIZE_IN_BYTES')); + it('missing file upload count limit should fail', async () => validateMissing(ALL_ENV, 'FILE_UPLOAD_COUNT_LIMIT')); + it('invalid file upload limit should fail', async () => shouldFailBadValues(ALL_ENV, 'FILE_UPLOAD_MAX_SIZE_IN_BYTES', [-1])); + + it('invalid file upload count limit should fail', async () => + shouldFailBadValues(ALL_ENV, 'FILE_UPLOAD_COUNT_LIMIT', [-1])); }); describe('valid environment', () => { @@ -52,6 +58,12 @@ describe('Content Publishing API Config', () => { ); }); + it('should get file upload count limit', () => { + expect(contentPublishingServiceConfig.fileUploadCountLimit).toStrictEqual( + parseInt(ALL_ENV.FILE_UPLOAD_COUNT_LIMIT as string, 10), + ); + }); + // it('should get api timeout limit milliseconds', () => { // expect(contentPublishingServiceConfig.apiTimeoutMs).toStrictEqual(parseInt(ALL_ENV.API_TIMEOUT_MS as string, 10)); // }); diff --git a/apps/content-publishing-api/src/api.config.ts b/apps/content-publishing-api/src/api.config.ts index 9347b3dd..9247f144 100644 --- a/apps/content-publishing-api/src/api.config.ts +++ b/apps/content-publishing-api/src/api.config.ts @@ -7,6 +7,7 @@ export interface IContentPublishingApiConfig { apiPort: number; // apiTimeoutMs: number; fileUploadMaxSizeBytes: number; + fileUploadCountLimit: number; } export default registerAs('content-publishing-api', (): IContentPublishingApiConfig => { @@ -27,6 +28,10 @@ export default registerAs('content-publishing-api', (): IContentPublishingApiCon value: process.env.FILE_UPLOAD_MAX_SIZE_IN_BYTES, joi: Joi.number().min(1).required(), }, + fileUploadCountLimit: { + value: process.env.FILE_UPLOAD_COUNT_LIMIT, + joi: Joi.number().min(1).required(), + }, }; return JoiUtils.validate(configs); diff --git a/apps/content-publishing-api/src/api.module.ts b/apps/content-publishing-api/src/api.module.ts index 1720d49a..2d330424 100644 --- a/apps/content-publishing-api/src/api.module.ts +++ b/apps/content-publishing-api/src/api.module.ts @@ -108,6 +108,7 @@ import ipfsConfig from '#content-publishing-lib/config/ipfs.config'; useFactory: async (apiConf: IContentPublishingApiConfig) => ({ limits: { fileSize: apiConf.fileUploadMaxSizeBytes, + files: apiConf.fileUploadCountLimit, }, }), inject: [apiConfig.KEY], diff --git a/apps/content-publishing-api/src/build-openapi.ts b/apps/content-publishing-api/src/build-openapi.ts index d2dfb272..afe494b2 100644 --- a/apps/content-publishing-api/src/build-openapi.ts +++ b/apps/content-publishing-api/src/build-openapi.ts @@ -22,6 +22,7 @@ process.env.CAPACITY_LIMIT = '{"type":"amount","value":"80"}'; process.env.IPFS_ENDPOINT = 'http://127.0.0.1'; process.env.IPFS_GATEWAY_URL = 'http://127.0.0.1'; process.env.FILE_UPLOAD_MAX_SIZE_IN_BYTES = '100'; +process.env.FILE_UPLOAD_COUNT_LIMIT = '10'; process.env.ASSET_EXPIRATION_INTERVAL_SECONDS = '100'; process.env.BATCH_INTERVAL_SECONDS = '100'; process.env.BATCH_MAX_COUNT = '100'; diff --git a/apps/content-publishing-api/src/controllers/v1/content.controller.v1.ts b/apps/content-publishing-api/src/controllers/v1/content.controller.v1.ts index 11d5cbed..6b296ce5 100644 --- a/apps/content-publishing-api/src/controllers/v1/content.controller.v1.ts +++ b/apps/content-publishing-api/src/controllers/v1/content.controller.v1.ts @@ -2,7 +2,6 @@ import { Body, Controller, Delete, HttpCode, Logger, Param, Post, Put } from '@n import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiService } from '../../api.service'; import { - DsnpUserIdParam, BroadcastDto, AnnouncementResponseDto, AssetIncludedRequestDto, @@ -12,6 +11,7 @@ import { TombstoneDto, } from '#types/dtos/content-publishing'; import { AnnouncementTypeName } from '#types/enums'; +import { MsaIdDto } from '#types/dtos/common'; @Controller('v1/content') @ApiTags('v1/content') @@ -22,60 +22,46 @@ export class ContentControllerV1 { this.logger = new Logger(this.constructor.name); } - @Post(':userDsnpId/broadcast') + @Post(':msaId/broadcast') @ApiOperation({ summary: 'Create DSNP Broadcast for user' }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async broadcast( - @Param() userDsnpId: DsnpUserIdParam, - @Body() broadcastDto: BroadcastDto, - ): Promise { + async broadcast(@Param() { msaId }: MsaIdDto, @Body() broadcastDto: BroadcastDto): Promise { const metadata = await this.apiService.validateAssetsAndFetchMetadata(broadcastDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest( - AnnouncementTypeName.BROADCAST, - userDsnpId.userDsnpId, - broadcastDto, - metadata, - ); + return this.apiService.enqueueRequest(AnnouncementTypeName.BROADCAST, msaId, broadcastDto, metadata); } - @Post(':userDsnpId/reply') + @Post(':msaId/reply') @ApiOperation({ summary: 'Create DSNP Reply for user' }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async reply(@Param() userDsnpId: DsnpUserIdParam, @Body() replyDto: ReplyDto): Promise { + async reply(@Param() { msaId }: MsaIdDto, @Body() replyDto: ReplyDto): Promise { const metadata = await this.apiService.validateAssetsAndFetchMetadata(replyDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeName.REPLY, userDsnpId.userDsnpId, replyDto, metadata); + return this.apiService.enqueueRequest(AnnouncementTypeName.REPLY, msaId, replyDto, metadata); } - @Post(':userDsnpId/reaction') + @Post(':msaId/reaction') @ApiOperation({ summary: 'Create DSNP Reaction for user' }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async reaction( - @Param() userDsnpId: DsnpUserIdParam, - @Body() reactionDto: ReactionDto, - ): Promise { - return this.apiService.enqueueRequest(AnnouncementTypeName.REACTION, userDsnpId.userDsnpId, reactionDto); + async reaction(@Param() { msaId }: MsaIdDto, @Body() reactionDto: ReactionDto): Promise { + return this.apiService.enqueueRequest(AnnouncementTypeName.REACTION, msaId, reactionDto); } - @Put(':userDsnpId') + @Put(':msaId') @ApiOperation({ summary: 'Update DSNP Content for user' }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async update(@Param() userDsnpId: DsnpUserIdParam, @Body() updateDto: UpdateDto): Promise { + async update(@Param() { msaId }: MsaIdDto, @Body() updateDto: UpdateDto): Promise { const metadata = await this.apiService.validateAssetsAndFetchMetadata(updateDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeName.UPDATE, userDsnpId.userDsnpId, updateDto, metadata); + return this.apiService.enqueueRequest(AnnouncementTypeName.UPDATE, msaId, updateDto, metadata); } - @Delete(':userDsnpId') + @Delete(':msaId') @ApiOperation({ summary: 'Delete DSNP Content for user' }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async delete( - @Param() userDsnpId: DsnpUserIdParam, - @Body() tombstoneDto: TombstoneDto, - ): Promise { - return this.apiService.enqueueRequest(AnnouncementTypeName.TOMBSTONE, userDsnpId.userDsnpId, tombstoneDto); + async delete(@Param() { msaId }: MsaIdDto, @Body() tombstoneDto: TombstoneDto): Promise { + return this.apiService.enqueueRequest(AnnouncementTypeName.TOMBSTONE, msaId, tombstoneDto); } } diff --git a/apps/content-publishing-api/src/controllers/v1/profile.controller.v1.ts b/apps/content-publishing-api/src/controllers/v1/profile.controller.v1.ts index e71ca4f3..3777126c 100644 --- a/apps/content-publishing-api/src/controllers/v1/profile.controller.v1.ts +++ b/apps/content-publishing-api/src/controllers/v1/profile.controller.v1.ts @@ -1,13 +1,9 @@ import { Body, Controller, HttpCode, Logger, Param, Put } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import { ApiService } from '../../api.service'; -import { - DsnpUserIdParam, - ProfileDto, - AnnouncementResponseDto, - AssetIncludedRequestDto, -} from '#types/dtos/content-publishing'; +import { ProfileDto, AnnouncementResponseDto, AssetIncludedRequestDto } from '#types/dtos/content-publishing'; import { AnnouncementTypeName } from '#types/enums'; +import { MsaIdDto } from '#types/dtos/common'; @Controller('v1/profile') @ApiTags('v1/profile') @@ -18,15 +14,12 @@ export class ProfileControllerV1 { this.logger = new Logger(this.constructor.name); } - @Put(':userDsnpId') + @Put(':msaId') @ApiOperation({ summary: "Update a user's Profile" }) @HttpCode(202) @ApiResponse({ status: '2XX', type: AnnouncementResponseDto }) - async profile( - @Param() userDsnpId: DsnpUserIdParam, - @Body() profileDto: ProfileDto, - ): Promise { + async profile(@Param() { msaId }: MsaIdDto, @Body() profileDto: ProfileDto): Promise { const metadata = await this.apiService.validateAssetsAndFetchMetadata(profileDto as AssetIncludedRequestDto); - return this.apiService.enqueueRequest(AnnouncementTypeName.PROFILE, userDsnpId.userDsnpId, profileDto, metadata); + return this.apiService.enqueueRequest(AnnouncementTypeName.PROFILE, msaId, profileDto, metadata); } } diff --git a/apps/content-publishing-api/src/metadata.ts b/apps/content-publishing-api/src/metadata.ts index b52a62de..3ea240af 100644 --- a/apps/content-publishing-api/src/metadata.ts +++ b/apps/content-publishing-api/src/metadata.ts @@ -2,7 +2,15 @@ export default async () => { const t = { ["../../../libs/types/src/dtos/content-publishing/activity.dto"]: await import("../../../libs/types/src/dtos/content-publishing/activity.dto"), - ["../../../libs/types/src/dtos/content-publishing/announcement.dto"]: await import("../../../libs/types/src/dtos/content-publishing/announcement.dto") + ["../../../libs/types/src/dtos/content-publishing/announcement.dto"]: await import("../../../libs/types/src/dtos/content-publishing/announcement.dto"), + ["../../../libs/types/src/dtos/account/graphs.request.dto"]: await import("../../../libs/types/src/dtos/account/graphs.request.dto"), + ["../../../libs/types/src/dtos/account/wallet.login.request.dto"]: await import("../../../libs/types/src/dtos/account/wallet.login.request.dto"), + ["../../../libs/types/src/dtos/account/accounts.response.dto"]: await import("../../../libs/types/src/dtos/account/accounts.response.dto"), + ["../../../libs/types/src/dtos/graph/privacy-type.enum"]: await import("../../../libs/types/src/dtos/graph/privacy-type.enum"), + ["../../../libs/types/src/dtos/graph/direction.enum"]: await import("../../../libs/types/src/dtos/graph/direction.enum"), + ["../../../libs/types/src/dtos/graph/connection-type.enum"]: await import("../../../libs/types/src/dtos/graph/connection-type.enum"), + ["../../../libs/types/src/dtos/graph/connection.dto"]: await import("../../../libs/types/src/dtos/graph/connection.dto"), + ["../../../libs/types/src/dtos/graph/key-type.enum"]: await import("../../../libs/types/src/dtos/graph/key-type.enum") }; - return { "@nestjs/swagger": { "models": [[import("../../../libs/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String } }, "AssetDto": { references: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/types/src/dtos/content-publishing/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/types/src/dtos/content-publishing/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/types/src/dtos/content-publishing/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/types/src/dtos/content-publishing/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] } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }]] } }; + return { "@nestjs/swagger": { "models": [[import("../../../libs/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/activity.dto"].UnitTypeDto } }, "AssetReferenceDto": { referenceId: { required: true, type: () => String, minLength: 1 }, height: { required: false, type: () => Number }, width: { required: false, type: () => Number }, duration: { required: false, type: () => String, pattern: "DURATION_REGEX" } }, "TagDto": { type: { required: true, enum: t["../../../libs/types/src/dtos/content-publishing/activity.dto"].TagTypeDto }, name: { required: false, type: () => String, minLength: 1 }, mentionedId: { required: false, type: () => String } }, "AssetDto": { references: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/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/types/src/dtos/content-publishing/activity.dto"].TagDto] }, location: { required: false, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].LocationDto } }, "NoteActivityDto": { content: { required: true, type: () => String, minLength: 1 }, published: { required: true, type: () => String }, assets: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/activity.dto"].AssetDto] } }, "ProfileActivityDto": { icon: { required: false, type: () => [t["../../../libs/types/src/dtos/content-publishing/activity.dto"].AssetReferenceDto] }, summary: { required: false, type: () => String }, published: { required: false, type: () => String } } }], [import("../../../libs/types/src/dtos/content-publishing/announcement.dto"), { "BroadcastDto": { content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].NoteActivityDto } }, "ReplyDto": { inReplyTo: { required: true, type: () => String }, content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].NoteActivityDto } }, "TombstoneDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/types/src/dtos/content-publishing/announcement.dto"].ModifiableAnnouncementTypeDto } }, "UpdateDto": { targetContentHash: { required: true, type: () => String }, targetAnnouncementType: { required: true, enum: t["../../../libs/types/src/dtos/content-publishing/announcement.dto"].ModifiableAnnouncementTypeDto }, content: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].NoteActivityDto } }, "ReactionDto": { emoji: { required: true, type: () => String, minLength: 1, pattern: "DSNP_EMOJI_REGEX" }, apply: { required: true, type: () => Number }, inReplyTo: { required: true, type: () => String } }, "ProfileDto": { profile: { required: true, type: () => t["../../../libs/types/src/dtos/content-publishing/activity.dto"].ProfileActivityDto } } }], [import("../../../libs/types/src/dtos/content-publishing/common.dto"), { "AnnouncementResponseDto": { referenceId: { required: true, type: () => String } }, "UploadResponseDto": { assetIds: { required: true, type: () => [String] } }, "FilesUploadDto": { files: { required: true, type: () => [Object] } } }], [import("../../../libs/types/src/dtos/common/params.dto"), { "MsaIdDto": { msaId: { required: true, type: () => String } }, "ProviderMsaIdDto": { providerId: { required: true, type: () => String } }, "UrlDto": { url: { required: true, type: () => String } }, "HandleDto": { newHandle: { required: true, type: () => String, minLength: 3 } }, "AccountIdDto": { accountId: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/graphs.request.dto"), { "ItemActionDto": { type: { required: true, enum: t["../../../libs/types/src/dtos/account/graphs.request.dto"].ItemActionType }, encodedPayload: { required: false, type: () => String }, index: { required: false, type: () => Number } }, "ItemizedSignaturePayloadDto": { schemaId: { required: true, type: () => Number }, targetHash: { required: true, type: () => Number }, expiration: { required: true, type: () => Number }, actions: { required: true, type: () => [t["../../../libs/types/src/dtos/account/graphs.request.dto"].ItemActionDto] } }, "AddNewPublicKeyAgreementRequestDto": { accountId: { required: true, type: () => String }, payload: { required: true, type: () => t["../../../libs/types/src/dtos/account/graphs.request.dto"].ItemizedSignaturePayloadDto }, proof: { required: true, type: () => String } }, "AddNewPublicKeyAgreementPayloadRequest": { payload: { required: true, type: () => t["../../../libs/types/src/dtos/account/graphs.request.dto"].ItemizedSignaturePayloadDto }, encodedPayload: { required: true, type: () => String } }, "PublicKeyAgreementsKeyPayload": { msaId: { required: true, type: () => String }, newKey: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/handles.request.dto"), { "HandleRequestDto": { accountId: { required: true, type: () => String }, proof: { required: true, type: () => String } }, "ChangeHandlePayloadRequest": { encodedPayload: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/wallet.login.request.dto"), { "ErrorResponseDto": { message: { required: true, type: () => String } }, "SiwsPayloadDto": { message: { required: true, type: () => String }, signature: { required: true, type: () => String } }, "SignInResponseDto": { siwsPayload: { required: false, type: () => t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].SiwsPayloadDto }, error: { required: false, type: () => t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].ErrorResponseDto } }, "EncodedExtrinsicDto": { pallet: { required: true, type: () => String, minLength: 1 }, extrinsicName: { required: true, type: () => String, minLength: 1 }, encodedExtrinsic: { required: true, type: () => String } }, "SignUpResponseDto": { extrinsics: { required: false, type: () => [t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].EncodedExtrinsicDto] }, error: { required: false, type: () => t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].ErrorResponseDto } }, "WalletLoginRequestDto": { signIn: { required: true, type: () => t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].SignInResponseDto }, signUp: { required: true, type: () => t["../../../libs/types/src/dtos/account/wallet.login.request.dto"].SignUpResponseDto } } }], [import("../../../libs/types/src/dtos/account/keys.request.dto"), { "KeysRequestDto": { msaOwnerAddress: { required: true, type: () => String }, msaOwnerSignature: { required: true, type: () => String }, newKeyOwnerSignature: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/accounts.response.dto"), { "HandleResponseDto": { base_handle: { required: true, type: () => String }, canonical_base: { required: true, type: () => String }, suffix: { required: true, type: () => Number } }, "AccountResponseDto": { msaId: { required: true, type: () => String }, handle: { required: false, type: () => t["../../../libs/types/src/dtos/account/accounts.response.dto"].HandleResponseDto } }, "MsaIdResponseDto": { msaId: { required: true, type: () => String } }, "RetireMsaPayloadResponseDto": { encodedExtrinsic: { required: true, type: () => String }, payloadToSign: { required: true, type: () => String }, accountId: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/accounts.request.dto"), { "RetireMsaRequestDto": { signature: { required: true, type: () => String }, accountId: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/account/revokeDelegation.request.dto"), { "RevokeDelegationPayloadResponseDto": { accountId: { required: true, type: () => String }, providerId: { required: true, type: () => String }, encodedExtrinsic: { required: true, type: () => String }, payloadToSign: { required: true, type: () => String } }, "RevokeDelegationPayloadRequestDto": { signature: { required: true, type: () => String } } }], [import("../../../libs/types/src/dtos/graph/connection.dto"), { "ConnectionDto": { dsnpId: { required: true, type: () => String }, privacyType: { required: true, enum: t["../../../libs/types/src/dtos/graph/privacy-type.enum"].PrivacyType }, direction: { required: true, enum: t["../../../libs/types/src/dtos/graph/direction.enum"].Direction }, connectionType: { required: true, enum: t["../../../libs/types/src/dtos/graph/connection-type.enum"].ConnectionType } }, "ConnectionDtoWrapper": { data: { required: true, type: () => [t["../../../libs/types/src/dtos/graph/connection.dto"].ConnectionDto] } } }], [import("../../../libs/types/src/dtos/graph/graph-key-pair.dto"), { "GraphKeyPairDto": { publicKey: { required: true, type: () => String }, privateKey: { required: true, type: () => String }, keyType: { required: true, type: () => String, enum: t["../../../libs/types/src/dtos/graph/key-type.enum"].KeyType } } }]], "controllers": [[import("./controllers/health.controller"), { "HealthController": { "healthz": {}, "livez": {}, "readyz": {} } }]] } }; }; \ No newline at end of file diff --git a/apps/content-publishing-api/test/mockRequestData.ts b/apps/content-publishing-api/test/mockRequestData.ts index c87ba608..122f784a 100644 --- a/apps/content-publishing-api/test/mockRequestData.ts +++ b/apps/content-publishing-api/test/mockRequestData.ts @@ -40,13 +40,13 @@ export const validBroadCastNoUploadedAssets = { export const validReplyNoUploadedAssets = { content: validContentNoUploadedAssets, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + inReplyTo: 'dsnp://78187493520/bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna', }; export const validReaction = { emoji: '🤌🏼', apply: 5, - inReplyTo: 'dsnp://78187493520/0x1234567890abcdef0123456789abcdef0123456789abcdef0123456789abcdef', + inReplyTo: 'dsnp://78187493520/bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna', }; export const validProfileNoUploadedAssets = { diff --git a/developer-docs/content-publishing/ENVIRONMENT.md b/developer-docs/content-publishing/ENVIRONMENT.md index 9983dd11..40f61b14 100644 --- a/developer-docs/content-publishing/ENVIRONMENT.md +++ b/developer-docs/content-publishing/ENVIRONMENT.md @@ -13,6 +13,7 @@ This application recognizes the following environment variables: | `CACHE_KEY_PREFIX` | Prefix to use for Redis cache keys | string | Y | | | `CAPACITY_LIMIT` | Maximum amount of provider capacity this app is allowed to use (per epoch) type: 'percentage' 'amount' value: number (may be percentage, ie '80', or absolute amount of capacity) | JSON [(example)](https://github.com/ProjectLibertyLabs/gateway/blob/main/env-files/content-publishing.template.env) | Y | | | `FILE_UPLOAD_MAX_SIZE_IN_BYTES` | Max file size (in bytes) allowed for asset upload | > 0 | Y | | +| `FILE_UPLOAD_COUNT_LIMIT` | Max number of files to be able to upload at the same time via one upload call | > 0 | Y | | | `FREQUENCY_URL` | Blockchain node address | http(s): or ws(s): URL | Y | | | `IPFS_BASIC_AUTH_SECRET` | If using Infura, put auth token here, or leave blank for Kubo RPC | string | | blank | | `IPFS_BASIC_AUTH_USER` | If using Infura, put Project ID here, or leave blank for Kubo RPC | string | | blank | diff --git a/docker-compose.yaml b/docker-compose.yaml index e8f0d699..e7729a75 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -24,6 +24,7 @@ x-common-environment: &common-environment x-content-publishing-env: &content-publishing-env START_PROCESS: content-publishing-api FILE_UPLOAD_MAX_SIZE_IN_BYTES: 2000000000 + FILE_UPLOAD_COUNT_LIMIT: 10 ASSET_EXPIRATION_INTERVAL_SECONDS: 300 BATCH_INTERVAL_SECONDS: 12 BATCH_MAX_COUNT: 1000 diff --git a/docs/content-publishing/index.html b/docs/content-publishing/index.html index ae70367c..c1af0978 100644 --- a/docs/content-publishing/index.html +++ b/docs/content-publishing/index.html @@ -12,349 +12,349 @@ margin: 0; } - -

Content Publishing Service API (1.0)

Download OpenAPI specification:Download

Content Publishing Service API

-

v1/asset

Upload asset files

Request Body schema: multipart/form-data
required

Asset files

-
files
required
Array of strings <binary> [ items <binary > ]

Responses

Response samples

Content type
application/json
{
  • "assetIds": [
    ]
}

v1/content

Create DSNP Broadcast for user

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Create DSNP Reply for user

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
inReplyTo
required
string
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "inReplyTo": "string",
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Create DSNP Reaction for user

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
emoji
required
string non-empty DSNP_EMOJI_REGEX
apply
required
number [ 0 .. 255 ]
inReplyTo
required
string

Responses

Request samples

Content type
application/json
{
  • "emoji": "string",
  • "apply": 255,
  • "inReplyTo": "string"
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Update DSNP Content for user

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
targetContentHash
required
string
targetAnnouncementType
required
string
Enum: "broadcast" "reply"
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "targetContentHash": "string",
  • "targetAnnouncementType": "broadcast",
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Delete DSNP Content for user

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
targetContentHash
required
string
targetAnnouncementType
required
string
Enum: "broadcast" "reply"

Responses

Request samples

Content type
application/json
{
  • "targetContentHash": "string",
  • "targetAnnouncementType": "broadcast"
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

v1/profile

Update a user's Profile

path Parameters
userDsnpId
required
string
Request Body schema: application/json
required
required
object (ProfileActivityDto)

Responses

Request samples

Content type
application/json
{
  • "profile": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

health

Check the health status of the service

Responses

Check the live status of the service

Responses

Check the ready status of the service

Responses

dev

Get a Job given a jobId

ONLY enabled when ENVIRONMENT="dev".

-
path Parameters
jobId
required
string

Responses

Get an Asset given an assetId

ONLY enabled when ENVIRONMENT="dev".

-
path Parameters
assetId
required
string

Responses

Create dummy announcement data

ONLY enabled when ENVIRONMENT="dev".

-
path Parameters
count
required
number

Responses

+ " fill="currentColor">

Content Publishing Service API (1.0)

Download OpenAPI specification:Download

Content Publishing Service API

+

v1/asset

Upload asset files

Request Body schema: multipart/form-data
required

Asset files

+
files
required
Array of strings <binary> [ items <binary > ]

Responses

Response samples

Content type
application/json
{
  • "assetIds": [
    ]
}

v1/content

Create DSNP Broadcast for user

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Create DSNP Reply for user

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
inReplyTo
required
string

Target DSNP Content URI

+
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "inReplyTo": "dsnp://78187493520/bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna",
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Create DSNP Reaction for user

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
emoji
required
string non-empty DSNP_EMOJI_REGEX

the encoded reaction emoji

+
apply
required
number

Indicates whether the emoji should be applied and if so, at what strength

+
inReplyTo
required
string

Target DSNP Content URI

+

Responses

Request samples

Content type
application/json
{
  • "emoji": "😀",
  • "apply": "1",
  • "inReplyTo": "dsnp://78187493520/bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna"
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Update DSNP Content for user

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
targetContentHash
required
string

Target DSNP Content Hash

+
targetAnnouncementType
required
string
Enum: "broadcast" "reply"

Target announcement type

+
required
object (NoteActivityDto)

Responses

Request samples

Content type
application/json
{
  • "targetContentHash": "bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna",
  • "targetAnnouncementType": "broadcast",
  • "content": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

Delete DSNP Content for user

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
targetContentHash
required
string

Target DSNP Content Hash

+
targetAnnouncementType
required
string
Enum: "broadcast" "reply"

Target announcement type

+

Responses

Request samples

Content type
application/json
{
  • "targetContentHash": "bdyqdua4t4pxgy37mdmjyqv3dejp5betyqsznimpneyujsur23yubzna",
  • "targetAnnouncementType": "broadcast"
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

v1/profile

Update a user's Profile

path Parameters
msaId
required
string
Example: 2

Msa Id of requested account

+
Request Body schema: application/json
required
required
object (ProfileActivityDto)

Responses

Request samples

Content type
application/json
{
  • "profile": {
    }
}

Response samples

Content type
application/json
{
  • "referenceId": "string"
}

health

Check the health status of the service

Responses

Check the live status of the service

Responses

Check the ready status of the service

Responses

dev

Get a Job given a jobId

ONLY enabled when ENVIRONMENT="dev".

+
path Parameters
jobId
required
string

Responses

Get an Asset given an assetId

ONLY enabled when ENVIRONMENT="dev".

+
path Parameters
assetId
required
string

Responses

Create dummy announcement data

ONLY enabled when ENVIRONMENT="dev".

+
path Parameters
count
required
number

Responses