From 70763f76056c185be99d81b38f6eecdb72d3d080 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 20 Oct 2023 09:38:05 +0200 Subject: [PATCH 01/12] refactoring meta data stuff to an module and independent endpoint --- apps/server/src/modules/board/board.module.ts | 2 - .../update-element-content.body.params.ts | 20 +++++++++- .../content-element-update.visitor.spec.ts | 25 ++++-------- .../service/content-element-update.visitor.ts | 23 ++++++----- .../service/content-element.service.spec.ts | 9 ----- .../board/service/content-element.service.ts | 6 +-- .../server/src/modules/board/service/index.ts | 1 - apps/server/src/modules/index.ts | 3 +- .../controller/dto/index.ts | 1 + .../dto/meta-tag-extractor.response.ts | 28 +++++++++++++ .../meta-tag-extractor/controller/index.ts | 2 + .../meta-tag-extractor.controller.ts | 39 +++++++++++++++++++ .../controller/post-link-url.body.params.ts | 11 ++++++ .../src/modules/meta-tag-extractor/index.ts | 4 ++ .../meta-tag-extractor-api.module.ts | 13 +++++++ .../meta-tag-extractor.config.ts | 8 ++++ .../meta-tag-extractor.module.ts | 24 ++++++++++++ .../meta-tag-extractor/service/index.ts | 1 + .../meta-tag-extractor.service.spec.ts} | 18 ++++----- .../service/meta-tag-extractor.service.ts} | 19 ++++----- .../modules/meta-tag-extractor/uc/index.ts | 1 + .../uc/meta-tag-extractor.uc.ts | 24 ++++++++++++ .../src/modules/server/server.module.ts | 11 ++++-- .../board/link-element.do.factory.ts | 2 +- 24 files changed, 227 insertions(+), 68 deletions(-) create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/dto/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.config.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/service/index.ts rename apps/server/src/modules/{board/service/open-graph-proxy.service.spec.ts => meta-tag-extractor/service/meta-tag-extractor.service.spec.ts} (77%) rename apps/server/src/modules/{board/service/open-graph-proxy.service.ts => meta-tag-extractor/service/meta-tag-extractor.service.ts} (56%) create mode 100644 apps/server/src/modules/meta-tag-extractor/uc/index.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts diff --git a/apps/server/src/modules/board/board.module.ts b/apps/server/src/modules/board/board.module.ts index fb04364b6c3..a002766b56d 100644 --- a/apps/server/src/modules/board/board.module.ts +++ b/apps/server/src/modules/board/board.module.ts @@ -14,7 +14,6 @@ import { ColumnBoardService, ColumnService, ContentElementService, - OpenGraphProxyService, SubmissionItemService, } from './service'; import { BoardDoCopyService, SchoolSpecificFileCopyServiceFactory } from './service/board-do-copy-service'; @@ -38,7 +37,6 @@ import { ColumnBoardCopyService } from './service/column-board-copy.service'; BoardDoCopyService, ColumnBoardCopyService, SchoolSpecificFileCopyServiceFactory, - OpenGraphProxyService, ], exports: [ BoardDoAuthorizableService, diff --git a/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts b/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts index 5eb0f239c1f..dd3b2ab2444 100644 --- a/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts +++ b/apps/server/src/modules/board/controller/dto/element/update-element-content.body.params.ts @@ -1,7 +1,7 @@ import { ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger'; import { ContentElementType, InputFormat } from '@shared/domain'; import { Type } from 'class-transformer'; -import { IsDate, IsEnum, IsMongoId, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { IsDate, IsEnum, IsMongoId, IsOptional, IsString, IsUrl, ValidateNested } from 'class-validator'; export abstract class ElementContentBody { @ApiProperty({ @@ -31,10 +31,26 @@ export class FileElementContentBody extends ElementContentBody { @ApiProperty() content!: FileContentBody; } + export class LinkContentBody { - @IsString() + @IsUrl() @ApiProperty({}) url!: string; + + @IsString() + @IsOptional() + @ApiProperty({}) + title?: string; + + @IsString() + @IsOptional() + @ApiProperty({}) + description?: string; + + @IsString() + @IsOptional() + @ApiProperty({}) + imageUrl?: string; } export class LinkElementContentBody extends ElementContentBody { diff --git a/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts b/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts index 8a8368fce2b..110f3cf2c95 100644 --- a/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts +++ b/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts @@ -13,7 +13,6 @@ import { } from '@shared/testing'; import { ExternalToolContentBody, FileContentBody, RichTextContentBody } from '../controller/dto'; import { ContentElementUpdateVisitor } from './content-element-update.visitor'; -import { OpenGraphProxyService } from './open-graph-proxy.service'; describe(ContentElementUpdateVisitor.name, () => { describe('when visiting an unsupported component', () => { @@ -25,8 +24,7 @@ describe(ContentElementUpdateVisitor.name, () => { content.text = 'a text'; content.inputFormat = InputFormat.RICH_TEXT_CK5; const submissionItem = submissionItemFactory.build(); - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { board, column, card, submissionItem, updater }; }; @@ -66,8 +64,7 @@ describe(ContentElementUpdateVisitor.name, () => { const content = new RichTextContentBody(); content.text = 'a text'; content.inputFormat = InputFormat.RICH_TEXT_CK5; - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { fileElement, updater }; }; @@ -84,8 +81,7 @@ describe(ContentElementUpdateVisitor.name, () => { const linkElement = linkElementFactory.build(); const content = new FileContentBody(); content.caption = 'a caption'; - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { linkElement, updater }; }; @@ -102,8 +98,7 @@ describe(ContentElementUpdateVisitor.name, () => { const richTextElement = richTextElementFactory.build(); const content = new FileContentBody(); content.caption = 'a caption'; - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { richTextElement, updater }; }; @@ -121,8 +116,7 @@ describe(ContentElementUpdateVisitor.name, () => { const content = new RichTextContentBody(); content.text = 'a text'; content.inputFormat = InputFormat.RICH_TEXT_CK5; - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { submissionContainerElement, updater }; }; @@ -140,8 +134,7 @@ describe(ContentElementUpdateVisitor.name, () => { const externalToolElement = externalToolElementFactory.build({ contextExternalToolId: undefined }); const content = new ExternalToolContentBody(); content.contextExternalToolId = new ObjectId().toHexString(); - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { externalToolElement, updater, content }; }; @@ -161,8 +154,7 @@ describe(ContentElementUpdateVisitor.name, () => { const content = new RichTextContentBody(); content.text = 'a text'; content.inputFormat = InputFormat.RICH_TEXT_CK5; - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { externalToolElement, updater }; }; @@ -178,8 +170,7 @@ describe(ContentElementUpdateVisitor.name, () => { const setup = () => { const externalToolElement = externalToolElementFactory.build(); const content = new ExternalToolContentBody(); - const openGraphProxyService = new OpenGraphProxyService(); - const updater = new ContentElementUpdateVisitor(content, openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); return { externalToolElement, updater }; }; diff --git a/apps/server/src/modules/board/service/content-element-update.visitor.ts b/apps/server/src/modules/board/service/content-element-update.visitor.ts index 0f75bcaee2d..86e3fb67985 100644 --- a/apps/server/src/modules/board/service/content-element-update.visitor.ts +++ b/apps/server/src/modules/board/service/content-element-update.visitor.ts @@ -22,13 +22,12 @@ import { RichTextContentBody, SubmissionContainerContentBody, } from '../controller/dto'; -import { OpenGraphProxyService } from './open-graph-proxy.service'; @Injectable() export class ContentElementUpdateVisitor implements BoardCompositeVisitorAsync { private readonly content: AnyElementContentBody; - constructor(content: AnyElementContentBody, private readonly openGraphProxyService: OpenGraphProxyService) { + constructor(content: AnyElementContentBody) { this.content = content; } @@ -55,13 +54,19 @@ export class ContentElementUpdateVisitor implements BoardCompositeVisitorAsync { async visitLinkElementAsync(linkElement: LinkElement): Promise { if (this.content instanceof LinkContentBody) { - const urlWithProtocol = /:\/\//.test(this.content.url) ? this.content.url : `https://${this.content.url}`; - linkElement.url = new URL(urlWithProtocol).toString(); - const openGraphData = await this.openGraphProxyService.fetchOpenGraphData(linkElement.url); - linkElement.title = openGraphData.title; - linkElement.description = openGraphData.description; - if (openGraphData.image) { - linkElement.imageUrl = openGraphData.image.url; + linkElement.url = new URL(this.content.url).toString(); + linkElement.title = this.content.title ?? ''; + linkElement.description = this.content.description ?? ''; + if (this.content.imageUrl) { + const isRelativeUrl = (url: string) => { + const fallbackHostname = 'https://www.fallback-url-if-url-is-relative.org'; + const imageUrlObject = new URL(url, fallbackHostname); + return imageUrlObject.origin === fallbackHostname; + }; + + if (isRelativeUrl(this.content.imageUrl)) { + linkElement.imageUrl = this.content.imageUrl; + } } return Promise.resolve(); } diff --git a/apps/server/src/modules/board/service/content-element.service.spec.ts b/apps/server/src/modules/board/service/content-element.service.spec.ts index b1326450089..a29da662b5c 100644 --- a/apps/server/src/modules/board/service/content-element.service.spec.ts +++ b/apps/server/src/modules/board/service/content-element.service.spec.ts @@ -26,7 +26,6 @@ import { import { BoardDoRepo } from '../repo'; import { BoardDoService } from './board-do.service'; import { ContentElementService } from './content-element.service'; -import { OpenGraphProxyService } from './open-graph-proxy.service'; describe(ContentElementService.name, () => { let module: TestingModule; @@ -34,7 +33,6 @@ describe(ContentElementService.name, () => { let boardDoRepo: DeepMocked; let boardDoService: DeepMocked; let contentElementFactory: DeepMocked; - let openGraphProxyService: DeepMocked; beforeAll(async () => { module = await Test.createTestingModule({ @@ -52,10 +50,6 @@ describe(ContentElementService.name, () => { provide: ContentElementFactory, useValue: createMock(), }, - { - provide: OpenGraphProxyService, - useValue: createMock(), - }, ], }).compile(); @@ -63,7 +57,6 @@ describe(ContentElementService.name, () => { boardDoRepo = module.get(BoardDoRepo); boardDoService = module.get(BoardDoService); contentElementFactory = module.get(ContentElementFactory); - openGraphProxyService = module.get(OpenGraphProxyService); await setupEntities(); }); @@ -265,8 +258,6 @@ describe(ContentElementService.name, () => { image: { url: 'https://my-open-graph-proxy.scvs.de/image/adefcb12ed3a' }, }; - openGraphProxyService.fetchOpenGraphData.mockResolvedValueOnce(imageResponse); - return { linkElement, content, card, imageResponse }; }; diff --git a/apps/server/src/modules/board/service/content-element.service.ts b/apps/server/src/modules/board/service/content-element.service.ts index a7c957173f3..0e8b64b406c 100644 --- a/apps/server/src/modules/board/service/content-element.service.ts +++ b/apps/server/src/modules/board/service/content-element.service.ts @@ -11,15 +11,13 @@ import { AnyElementContentBody } from '../controller/dto'; import { BoardDoRepo } from '../repo'; import { BoardDoService } from './board-do.service'; import { ContentElementUpdateVisitor } from './content-element-update.visitor'; -import { OpenGraphProxyService } from './open-graph-proxy.service'; @Injectable() export class ContentElementService { constructor( private readonly boardDoRepo: BoardDoRepo, private readonly boardDoService: BoardDoService, - private readonly contentElementFactory: ContentElementFactory, - private readonly openGraphProxyService: OpenGraphProxyService + private readonly contentElementFactory: ContentElementFactory ) {} async findById(elementId: EntityId): Promise { @@ -48,7 +46,7 @@ export class ContentElementService { } async update(element: AnyContentElementDo, content: AnyElementContentBody): Promise { - const updater = new ContentElementUpdateVisitor(content, this.openGraphProxyService); + const updater = new ContentElementUpdateVisitor(content); await element.acceptAsync(updater); const parent = await this.boardDoRepo.findParentOfId(element.id); diff --git a/apps/server/src/modules/board/service/index.ts b/apps/server/src/modules/board/service/index.ts index ac9c686d4b4..8ff2787f35d 100644 --- a/apps/server/src/modules/board/service/index.ts +++ b/apps/server/src/modules/board/service/index.ts @@ -4,5 +4,4 @@ export * from './card.service'; export * from './column-board.service'; export * from './column.service'; export * from './content-element.service'; -export * from './open-graph-proxy.service'; export * from './submission-item.service'; diff --git a/apps/server/src/modules/index.ts b/apps/server/src/modules/index.ts index 111a64d9f33..b20982374aa 100644 --- a/apps/server/src/modules/index.ts +++ b/apps/server/src/modules/index.ts @@ -7,14 +7,15 @@ export * from './files-storage'; export * from './files-storage-client'; export * from './fwu-learning-contents'; export * from './learnroom'; +export * from './legacy-school'; export * from './lesson'; +export * from './meta-tag-extractor'; export * from './news'; export * from './oauth'; export * from './oauth-provider'; export * from './provisioning'; export * from './rocketchat'; export * from './role'; -export * from './legacy-school'; export * from './sharing'; export * from './system'; export * from './task'; diff --git a/apps/server/src/modules/meta-tag-extractor/controller/dto/index.ts b/apps/server/src/modules/meta-tag-extractor/controller/dto/index.ts new file mode 100644 index 00000000000..f4f64d6113d --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/dto/index.ts @@ -0,0 +1 @@ +export * from './meta-tag-extractor.response'; diff --git a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts new file mode 100644 index 00000000000..a2f5acd8465 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { DecodeHtmlEntities } from '@shared/controller'; +import { IsString, IsUrl } from 'class-validator'; + +export class MetaTagExtractorResponse { + constructor({ url, title, description, imageUrl }: MetaTagExtractorResponse) { + this.url = url; + this.title = title; + this.description = description; + this.imageUrl = imageUrl; + } + + @ApiProperty() + @IsUrl() + url!: string; + + @ApiProperty() + @DecodeHtmlEntities() + title?: string; + + @ApiProperty() + @DecodeHtmlEntities() + description?: string; + + @ApiProperty() + @IsString() + imageUrl?: string; +} diff --git a/apps/server/src/modules/meta-tag-extractor/controller/index.ts b/apps/server/src/modules/meta-tag-extractor/controller/index.ts new file mode 100644 index 00000000000..296b343c591 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/index.ts @@ -0,0 +1,2 @@ +export * from './dto'; +export * from './meta-tag-extractor.controller'; diff --git a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts new file mode 100644 index 00000000000..e9a8df0434e --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts @@ -0,0 +1,39 @@ +import { + BadRequestException, + Body, + Controller, + ForbiddenException, + InternalServerErrorException, + Post, +} from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiValidationError } from '@shared/common'; +import { ICurrentUser } from '@src/modules/authentication'; +import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; +import { MetaTagExtractorUc } from '../uc'; +import { MetaTagExtractorResponse } from './dto'; +import { GetMetaTagDataBody } from './post-link-url.body.params'; + +@ApiTags('Meta Tag Extractor') +@Authenticate('jwt') +@Controller('meta-tag-extractor') +export class MetaTagExtractorController { + constructor(private readonly metaTagExtractorUc: MetaTagExtractorUc) {} + + @ApiOperation({ summary: 'Return dummy HTML for testing' }) + @ApiResponse({ status: 201, type: MetaTagExtractorResponse }) + @ApiResponse({ status: 400, type: ApiValidationError }) + @ApiResponse({ status: 400, type: BadRequestException }) + @ApiResponse({ status: 403, type: ForbiddenException }) + @ApiResponse({ status: 500, type: InternalServerErrorException }) + @Post('/:url') + async getData( + @CurrentUser() currentUser: ICurrentUser, + @Body() bodyParams: GetMetaTagDataBody + ): Promise { + const result = await this.metaTagExtractorUc.fetchMetaData(currentUser.userId, bodyParams.url); + const imageUrl = result.image?.url; + const response = new MetaTagExtractorResponse({ ...result, imageUrl }); + return response; + } +} diff --git a/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts b/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts new file mode 100644 index 00000000000..1e9cd1f7f34 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/post-link-url.body.params.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsUrl } from 'class-validator'; + +export class GetMetaTagDataBody { + @IsUrl() + @ApiProperty({ + required: true, + nullable: false, + }) + url!: string; +} diff --git a/apps/server/src/modules/meta-tag-extractor/index.ts b/apps/server/src/modules/meta-tag-extractor/index.ts new file mode 100644 index 00000000000..fb549460609 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/index.ts @@ -0,0 +1,4 @@ +export * from './controller'; +export * from './meta-tag-extractor-api.module'; +export * from './meta-tag-extractor.module'; +export * from './uc'; diff --git a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts new file mode 100644 index 00000000000..d9095315e87 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor-api.module.ts @@ -0,0 +1,13 @@ +import { forwardRef, Module } from '@nestjs/common'; +import { LoggerModule } from '@src/core/logger'; +import { AuthorizationModule } from '@src/modules/authorization'; +import { MetaTagExtractorController } from './controller'; +import { MetaTagExtractorModule } from './meta-tag-extractor.module'; +import { MetaTagExtractorUc } from './uc'; + +@Module({ + imports: [MetaTagExtractorModule, LoggerModule, forwardRef(() => AuthorizationModule)], + controllers: [MetaTagExtractorController], + providers: [MetaTagExtractorUc], +}) +export class MetaTagExtractorApiModule {} diff --git a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.config.ts b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.config.ts new file mode 100644 index 00000000000..d82e6811009 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.config.ts @@ -0,0 +1,8 @@ +import { Configuration } from '@hpi-schul-cloud/commons'; + +const metaTagExtractorConfig = { + NEST_LOG_LEVEL: Configuration.get('NEST_LOG_LEVEL') as string, + INCOMING_REQUEST_TIMEOUT: Configuration.get('INCOMING_REQUEST_TIMEOUT_API') as number, +}; + +export default () => metaTagExtractorConfig; diff --git a/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts new file mode 100644 index 00000000000..817d7257330 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/meta-tag-extractor.module.ts @@ -0,0 +1,24 @@ +import { HttpModule } from '@nestjs/axios'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { ConsoleWriterModule } from '@shared/infra/console'; +import { createConfigModuleOptions } from '@src/config'; +import { LoggerModule } from '@src/core/logger'; +import { AuthenticationModule } from '../authentication/authentication.module'; +import { UserModule } from '../user'; +import metaTagExtractorConfig from './meta-tag-extractor.config'; +import { MetaTagExtractorService } from './service'; + +@Module({ + imports: [ + AuthenticationModule, + ConsoleWriterModule, + HttpModule, + LoggerModule, + UserModule, + ConfigModule.forRoot(createConfigModuleOptions(metaTagExtractorConfig)), + ], + providers: [MetaTagExtractorService], + exports: [MetaTagExtractorService], +}) +export class MetaTagExtractorModule {} diff --git a/apps/server/src/modules/meta-tag-extractor/service/index.ts b/apps/server/src/modules/meta-tag-extractor/service/index.ts new file mode 100644 index 00000000000..238c426e837 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/service/index.ts @@ -0,0 +1 @@ +export * from './meta-tag-extractor.service'; diff --git a/apps/server/src/modules/board/service/open-graph-proxy.service.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts similarity index 77% rename from apps/server/src/modules/board/service/open-graph-proxy.service.spec.ts rename to apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts index debe76cdeba..0f205b57b63 100644 --- a/apps/server/src/modules/board/service/open-graph-proxy.service.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts @@ -1,7 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities } from '@shared/testing'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; -import { OpenGraphProxyService } from './open-graph-proxy.service'; +import { MetaTagExtractorService } from './meta-tag-extractor.service'; let ogsResponseMock = {}; jest.mock( @@ -15,16 +15,16 @@ jest.mock( }) ); -describe(OpenGraphProxyService.name, () => { +describe(MetaTagExtractorService.name, () => { let module: TestingModule; - let service: OpenGraphProxyService; + let service: MetaTagExtractorService; beforeAll(async () => { module = await Test.createTestingModule({ - providers: [OpenGraphProxyService], + providers: [MetaTagExtractorService], }).compile(); - service = module.get(OpenGraphProxyService); + service = module.get(MetaTagExtractorService); await setupEntities(); }); @@ -41,7 +41,7 @@ describe(OpenGraphProxyService.name, () => { it('should return also the original url', async () => { const url = 'https://de.wikipedia.org'; - const result = await service.fetchOpenGraphData(url); + const result = await service.fetchMetaData(url); expect(result).toEqual(expect.objectContaining({ url })); }); @@ -49,7 +49,7 @@ describe(OpenGraphProxyService.name, () => { it('should thrown an error if url is an empty string', async () => { const url = ''; - await expect(service.fetchOpenGraphData(url)).rejects.toThrow(); + await expect(service.fetchMetaData(url)).rejects.toThrow(); }); it('should return ogTitle as title', async () => { @@ -57,7 +57,7 @@ describe(OpenGraphProxyService.name, () => { const url = 'https://de.wikipedia.org'; ogsResponseMock = { ogTitle }; - const result = await service.fetchOpenGraphData(url); + const result = await service.fetchMetaData(url); expect(result).toEqual(expect.objectContaining({ title: ogTitle })); }); @@ -83,7 +83,7 @@ describe(OpenGraphProxyService.name, () => { const url = 'https://de.wikipedia.org'; ogsResponseMock = { ogImage }; - const result = await service.fetchOpenGraphData(url); + const result = await service.fetchMetaData(url); expect(result).toEqual(expect.objectContaining({ image: ogImage[1] })); }); diff --git a/apps/server/src/modules/board/service/open-graph-proxy.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts similarity index 56% rename from apps/server/src/modules/board/service/open-graph-proxy.service.ts rename to apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index 2b54d75ee82..c700f7ec5c3 100644 --- a/apps/server/src/modules/board/service/open-graph-proxy.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -1,8 +1,9 @@ import { Injectable } from '@nestjs/common'; +// import fetch from 'node-fetch'; import ogs from 'open-graph-scraper'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; -type OpenGraphData = { +export type MetaData = { title: string; description: string; url: string; @@ -10,18 +11,18 @@ type OpenGraphData = { }; @Injectable() -export class OpenGraphProxyService { - async fetchOpenGraphData(url: string): Promise { +export class MetaTagExtractorService { + // WIP: add nice debug logging for available open GraphData?!? + async fetchMetaData(url: string): Promise { if (url.length === 0) { - throw new Error(`OpenGraphProxyService requires a valid URL. Given URL: ${url}`); + throw new Error(`MetaTagExtractorService requires a valid URL. Given URL: ${url}`); } - const data = await ogs({ url }); - // WIP: add nice debug logging for available openGraphData?!? + const data = await ogs({ url, fetchOptions: { headers: { 'User-Agent': 'Open Graph Scraper' } } }); - const title = data.result.ogTitle ?? ''; - const description = data.result.ogDescription ?? ''; - const image = data.result.ogImage ? this.pickImage(data.result.ogImage) : undefined; + const title = data.result?.ogTitle ?? ''; + const description = data?.result?.ogDescription ?? ''; + const image = data?.result?.ogImage ? this.pickImage(data?.result?.ogImage) : undefined; return { title, diff --git a/apps/server/src/modules/meta-tag-extractor/uc/index.ts b/apps/server/src/modules/meta-tag-extractor/uc/index.ts new file mode 100644 index 00000000000..6621180a92b --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/uc/index.ts @@ -0,0 +1 @@ +export * from './meta-tag-extractor.uc'; diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts new file mode 100644 index 00000000000..d7264f6d193 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { EntityId } from '@shared/domain'; +import { LegacyLogger } from '@src/core/logger'; +import { MetaData, MetaTagExtractorService } from '../service'; + +@Injectable() +export class MetaTagExtractorUc { + constructor( + // private readonly authorizationService: AuthorizationService, + private readonly metaTagExtractorService: MetaTagExtractorService, + private readonly logger: LegacyLogger + ) { + this.logger.setContext(MetaTagExtractorUc.name); + } + + async fetchMetaData(userId: EntityId, url: string): Promise { + this.logger.debug({ action: 'fetchMetaData', userId }); + + const result = await this.metaTagExtractorService.fetchMetaData(url); + // WIP: check permission + + return result; + } +} diff --git a/apps/server/src/modules/server/server.module.ts b/apps/server/src/modules/server/server.module.ts index 20b346929cd..c0ba8791531 100644 --- a/apps/server/src/modules/server/server.module.ts +++ b/apps/server/src/modules/server/server.module.ts @@ -7,7 +7,7 @@ import { ALL_ENTITIES } from '@shared/domain'; import { MongoDatabaseModuleOptions, MongoMemoryDatabaseModule } from '@shared/infra/database'; import { MailModule } from '@shared/infra/mail'; import { RabbitMQWrapperModule, RabbitMQWrapperTestModule } from '@shared/infra/rabbitmq'; -import { REDIS_CLIENT, RedisModule } from '@shared/infra/redis'; +import { RedisModule, REDIS_CLIENT } from '@shared/infra/redis'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LegacyLogger, LoggerModule } from '@src/core/logger'; @@ -18,15 +18,17 @@ import { CollaborativeStorageModule } from '@src/modules/collaborative-storage'; import { FilesStorageClientModule } from '@src/modules/files-storage-client'; import { GroupApiModule } from '@src/modules/group/group-api.module'; import { LearnroomApiModule } from '@src/modules/learnroom/learnroom-api.module'; +import { LegacySchoolApiModule } from '@src/modules/legacy-school/legacy-school-api.module'; import { LessonApiModule } from '@src/modules/lesson/lesson-api.module'; import { NewsModule } from '@src/modules/news'; import { OauthProviderApiModule } from '@src/modules/oauth-provider'; import { OauthApiModule } from '@src/modules/oauth/oauth-api.module'; +import { PseudonymApiModule } from '@src/modules/pseudonym/pseudonym-api.module'; import { RocketChatModule } from '@src/modules/rocketchat'; -import { LegacySchoolApiModule } from '@src/modules/legacy-school/legacy-school-api.module'; import { SharingApiModule } from '@src/modules/sharing/sharing.module'; import { SystemApiModule } from '@src/modules/system/system-api.module'; import { TaskApiModule } from '@src/modules/task/task-api.module'; +import { TeamsApiModule } from '@src/modules/teams/teams-api.module'; import { ToolApiModule } from '@src/modules/tool/tool-api.module'; import { ImportUserModule } from '@src/modules/user-import'; import { UserLoginMigrationApiModule } from '@src/modules/user-login-migration/user-login-migration-api.module'; @@ -35,8 +37,7 @@ import { VideoConferenceApiModule } from '@src/modules/video-conference/video-co import connectRedis from 'connect-redis'; import session from 'express-session'; import { RedisClient } from 'redis'; -import { TeamsApiModule } from '@src/modules/teams/teams-api.module'; -import { PseudonymApiModule } from '@src/modules/pseudonym/pseudonym-api.module'; +import { MetaTagExtractorApiModule, MetaTagExtractorModule } from '../meta-tag-extractor'; import { ServerController } from './controller/server.controller'; import { serverConfig } from './server.config'; @@ -47,6 +48,7 @@ const serverModules = [ AccountApiModule, CollaborativeStorageModule, OauthApiModule, + MetaTagExtractorModule, TaskApiModule, LessonApiModule, NewsModule, @@ -75,6 +77,7 @@ const serverModules = [ BoardApiModule, GroupApiModule, TeamsApiModule, + MetaTagExtractorApiModule, PseudonymApiModule, ]; diff --git a/apps/server/src/shared/testing/factory/domainobject/board/link-element.do.factory.ts b/apps/server/src/shared/testing/factory/domainobject/board/link-element.do.factory.ts index af0e55a1912..415cfeae1dc 100644 --- a/apps/server/src/shared/testing/factory/domainobject/board/link-element.do.factory.ts +++ b/apps/server/src/shared/testing/factory/domainobject/board/link-element.do.factory.ts @@ -7,7 +7,7 @@ export const linkElementFactory = BaseFactory.define Date: Mon, 23 Oct 2023 09:25:32 +0200 Subject: [PATCH 02/12] chore: remove commented out line --- .../meta-tag-extractor/service/meta-tag-extractor.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index c700f7ec5c3..094c0fa296a 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -// import fetch from 'node-fetch'; import ogs from 'open-graph-scraper'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; From 344fab333bb91e19d491dd168fbf2880ee6adbdb Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Mon, 23 Oct 2023 18:24:34 +0200 Subject: [PATCH 03/12] chore: implement fallback of linked server does not respond --- .../service/meta-tag-extractor.service.ts | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index 094c0fa296a..8f043276357 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -11,24 +11,33 @@ export type MetaData = { @Injectable() export class MetaTagExtractorService { - // WIP: add nice debug logging for available open GraphData?!? async fetchMetaData(url: string): Promise { if (url.length === 0) { + // WIP: add nice debug logging for available open GraphData?!? throw new Error(`MetaTagExtractorService requires a valid URL. Given URL: ${url}`); } - const data = await ogs({ url, fetchOptions: { headers: { 'User-Agent': 'Open Graph Scraper' } } }); + try { + const data = await ogs({ url, fetchOptions: { headers: { 'User-Agent': 'Open Graph Scraper' } } }); - const title = data.result?.ogTitle ?? ''; - const description = data?.result?.ogDescription ?? ''; - const image = data?.result?.ogImage ? this.pickImage(data?.result?.ogImage) : undefined; + const title = data.result?.ogTitle ?? ''; + const description = data.result?.ogDescription ?? ''; + const image = data.result?.ogImage ? this.pickImage(data?.result?.ogImage) : undefined; - return { - title, - description, - image, - url, - }; + return { + title, + description, + image, + url, + }; + } catch (error) { + // WIP: add nice debug logging for available open GraphData?!? + return { + title: '', + description: '', + url, + }; + } } private pickImage(images: ImageObject[], minWidth = 400): ImageObject | undefined { From e8093ffb606613520b7265350df3623fab919d40 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Tue, 24 Oct 2023 18:39:32 +0200 Subject: [PATCH 04/12] feature: implement fallback to filename if no open graph data --- .../service/meta-tag-extractor.service.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index 8f043276357..f99959057ba 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -1,6 +1,7 @@ import { Injectable } from '@nestjs/common'; import ogs from 'open-graph-scraper'; import { ImageObject } from 'open-graph-scraper/dist/lib/types'; +import { basename } from 'path'; export type MetaData = { title: string; @@ -17,6 +18,12 @@ export class MetaTagExtractorService { throw new Error(`MetaTagExtractorService requires a valid URL. Given URL: ${url}`); } + const metaData = (await this.tryExtractMetaTags(url)) ?? this.tryFilenameAsFallback(url); + + return metaData ?? { url, title: '', description: '' }; + } + + private async tryExtractMetaTags(url: string): Promise { try { const data = await ogs({ url, fetchOptions: { headers: { 'User-Agent': 'Open Graph Scraper' } } }); @@ -31,12 +38,21 @@ export class MetaTagExtractorService { url, }; } catch (error) { - // WIP: add nice debug logging for available open GraphData?!? + return undefined; + } + } + + private tryFilenameAsFallback(url: string): MetaData | undefined { + try { + const urlObject = new URL(url); + const title = basename(urlObject.pathname); return { - title: '', + title, description: '', url, }; + } catch (error) { + return undefined; } } From 7f6396a097d9202caeec94a05e7153758f390d7e Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 25 Oct 2023 17:27:31 +0200 Subject: [PATCH 05/12] chore: extend update visitor tests --- .../content-element-update.visitor.spec.ts | 82 +++++++++++++++---- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts b/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts index 110f3cf2c95..0b55dfdb1de 100644 --- a/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts +++ b/apps/server/src/modules/board/service/content-element-update.visitor.spec.ts @@ -11,7 +11,7 @@ import { submissionContainerElementFactory, submissionItemFactory, } from '@shared/testing'; -import { ExternalToolContentBody, FileContentBody, RichTextContentBody } from '../controller/dto'; +import { ExternalToolContentBody, FileContentBody, LinkContentBody, RichTextContentBody } from '../controller/dto'; import { ContentElementUpdateVisitor } from './content-element-update.visitor'; describe(ContentElementUpdateVisitor.name, () => { @@ -76,23 +76,6 @@ describe(ContentElementUpdateVisitor.name, () => { }); }); - describe('when visiting a link element using the wrong content', () => { - const setup = () => { - const linkElement = linkElementFactory.build(); - const content = new FileContentBody(); - content.caption = 'a caption'; - const updater = new ContentElementUpdateVisitor(content); - - return { linkElement, updater }; - }; - - it('should throw an error', async () => { - const { linkElement, updater } = setup(); - - await expect(() => updater.visitLinkElementAsync(linkElement)).rejects.toThrow(); - }); - }); - describe('when visiting a rich text element using the wrong content', () => { const setup = () => { const richTextElement = richTextElementFactory.build(); @@ -128,6 +111,69 @@ describe(ContentElementUpdateVisitor.name, () => { }); }); + describe('when visiting a link element', () => { + describe('when content is valid', () => { + const setup = () => { + const linkElement = linkElementFactory.build(); + const content = new LinkContentBody(); + content.url = 'https://super-example.com/'; + content.title = 'SuperExample - the best examples in the web'; + content.imageUrl = '/preview/image.jpg'; + const updater = new ContentElementUpdateVisitor(content); + + return { linkElement, content, updater }; + }; + + it('should update the content', async () => { + const { linkElement, content, updater } = setup(); + + await updater.visitLinkElementAsync(linkElement); + + expect(linkElement.url).toEqual(content.url); + expect(linkElement.title).toEqual(content.title); + expect(linkElement.imageUrl).toEqual(content.imageUrl); + }); + }); + + describe('when content is not a link element', () => { + const setup = () => { + const linkElement = linkElementFactory.build(); + const content = new FileContentBody(); + content.caption = 'a caption'; + const updater = new ContentElementUpdateVisitor(content); + + return { linkElement, updater }; + }; + + it('should throw an error', async () => { + const { linkElement, updater } = setup(); + + await expect(() => updater.visitLinkElementAsync(linkElement)).rejects.toThrow(); + }); + }); + + describe('when imageUrl for preview image is not a relative url', () => { + const setup = () => { + const linkElement = linkElementFactory.build(); + const content = new LinkContentBody(); + content.url = 'https://super-example.com/'; + content.title = 'SuperExample - the best examples in the web'; + content.imageUrl = 'https://www.external.de/fake-preview-image.jpg'; + const updater = new ContentElementUpdateVisitor(content); + + return { linkElement, content, updater }; + }; + + it('should ignore the image url', async () => { + const { linkElement, updater } = setup(); + + await updater.visitLinkElementAsync(linkElement); + + expect(linkElement.imageUrl).toBe(''); + }); + }); + }); + describe('when visiting a external tool element', () => { describe('when visiting a external tool element with valid content', () => { const setup = () => { From 5a7354612a59271a2c898f3bd89b9b8adf1ae5c9 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 25 Oct 2023 17:53:38 +0200 Subject: [PATCH 06/12] chore: coverage of meta tag extractor service --- .../meta-tag-extractor.service.spec.ts | 135 +++++++++++------- 1 file changed, 83 insertions(+), 52 deletions(-) diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts index 0f205b57b63..af1a256d121 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.spec.ts @@ -4,16 +4,20 @@ import { ImageObject } from 'open-graph-scraper/dist/lib/types'; import { MetaTagExtractorService } from './meta-tag-extractor.service'; let ogsResponseMock = {}; -jest.mock( - 'open-graph-scraper', - () => () => - Promise.resolve({ - error: false, - html: '', - response: {}, - result: ogsResponseMock, - }) -); +let ogsRejectMock: Error | undefined; + +jest.mock('open-graph-scraper', () => () => { + if (ogsRejectMock) { + return Promise.reject(ogsRejectMock); + } + + return Promise.resolve({ + error: false, + html: '', + response: {}, + result: ogsResponseMock, + }); +}); describe(MetaTagExtractorService.name, () => { let module: TestingModule; @@ -33,59 +37,86 @@ describe(MetaTagExtractorService.name, () => { await module.close(); }); + beforeEach(() => { + ogsResponseMock = {}; + ogsRejectMock = undefined; + }); + afterEach(() => { jest.resetAllMocks(); }); describe('create', () => { - it('should return also the original url', async () => { - const url = 'https://de.wikipedia.org'; - - const result = await service.fetchMetaData(url); - - expect(result).toEqual(expect.objectContaining({ url })); + describe('when url points to webpage', () => { + it('should return also the original url', async () => { + const url = 'https://de.wikipedia.org'; + + const result = await service.fetchMetaData(url); + + expect(result).toEqual(expect.objectContaining({ url })); + }); + + it('should thrown an error if url is an empty string', async () => { + const url = ''; + + await expect(service.fetchMetaData(url)).rejects.toThrow(); + }); + + it('should return ogTitle as title', async () => { + const ogTitle = 'My Title'; + const url = 'https://de.wikipedia.org'; + ogsResponseMock = { ogTitle }; + + const result = await service.fetchMetaData(url); + + expect(result).toEqual(expect.objectContaining({ title: ogTitle })); + }); + + it('should return ogImage as image', async () => { + const ogImage: ImageObject[] = [ + { + width: 800, + type: 'jpeg', + url: 'big-image.jpg', + }, + { + width: 500, + type: 'jpeg', + url: 'medium-image.jpg', + }, + { + width: 300, + type: 'jpeg', + url: 'small-image.jpg', + }, + ]; + const url = 'https://de.wikipedia.org'; + ogsResponseMock = { ogImage }; + + const result = await service.fetchMetaData(url); + + expect(result).toEqual(expect.objectContaining({ image: ogImage[1] })); + }); }); - it('should thrown an error if url is an empty string', async () => { - const url = ''; + describe('when url points to a file', () => { + it('should return filename as title', async () => { + const url = 'https://de.wikipedia.org/abc.jpg'; + ogsRejectMock = new Error('no open graph data included... probably not a webpage'); - await expect(service.fetchMetaData(url)).rejects.toThrow(); + const result = await service.fetchMetaData(url); + expect(result).toEqual(expect.objectContaining({ title: 'abc.jpg' })); + }); }); - it('should return ogTitle as title', async () => { - const ogTitle = 'My Title'; - const url = 'https://de.wikipedia.org'; - ogsResponseMock = { ogTitle }; - - const result = await service.fetchMetaData(url); - - expect(result).toEqual(expect.objectContaining({ title: ogTitle })); - }); + describe('when url is invalid', () => { + it('should return url as it is', async () => { + const url = 'not-a-real-domain'; + ogsRejectMock = new Error('no open graph data included... probably not a webpage'); - it('should return ogImage as title', async () => { - const ogImage: ImageObject[] = [ - { - width: 800, - type: 'jpeg', - url: 'big-image.jpg', - }, - { - width: 500, - type: 'jpeg', - url: 'medium-image.jpg', - }, - { - width: 300, - type: 'jpeg', - url: 'small-image.jpg', - }, - ]; - const url = 'https://de.wikipedia.org'; - ogsResponseMock = { ogImage }; - - const result = await service.fetchMetaData(url); - - expect(result).toEqual(expect.objectContaining({ image: ogImage[1] })); + const result = await service.fetchMetaData(url); + expect(result).toEqual(expect.objectContaining({ url, title: '', description: '' })); + }); }); }); }); From 2590b94a576b38cef2eb011bb85e3b8409933a1f Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Wed, 1 Nov 2023 13:08:04 +0100 Subject: [PATCH 07/12] chore: add tests --- .../dto/meta-tag-extractor.response.spec.ts | 20 +++++++ .../meta-tag-extractor.controller.spec.ts | 58 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts diff --git a/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts new file mode 100644 index 00000000000..29dfbd94c72 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/dto/meta-tag-extractor.response.spec.ts @@ -0,0 +1,20 @@ +import { MetaTagExtractorResponse } from './meta-tag-extractor.response'; + +describe(MetaTagExtractorResponse.name, () => { + describe('when creating a error response', () => { + it('should have basic properties defined', () => { + const properties: MetaTagExtractorResponse = { + url: 'https://www.abc.de/my-article', + title: 'Testbild', + description: 'Here we describe what this page is about.', + imageUrl: 'https://www.abc.de/test.png', + }; + + const errorResponse = new MetaTagExtractorResponse(properties); + expect(errorResponse.url).toEqual(properties.url); + expect(errorResponse.title).toEqual(properties.title); + expect(errorResponse.description).toEqual(properties.description); + expect(errorResponse.imageUrl).toEqual(properties.imageUrl); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts new file mode 100644 index 00000000000..399c0261426 --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts @@ -0,0 +1,58 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ICurrentUser } from '@modules/authentication'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MetaData } from '../service'; +import { MetaTagExtractorUc } from '../uc'; +import { MetaTagExtractorController } from './meta-tag-extractor.controller'; + +describe('TaskController', () => { + let module: TestingModule; + let controller: MetaTagExtractorController; + let uc: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + { + provide: MetaTagExtractorUc, + useValue: createMock(), + }, + ], + controllers: [MetaTagExtractorController], + }).compile(); + + controller = module.get(MetaTagExtractorController); + uc = module.get(MetaTagExtractorUc); + }); + + afterAll(async () => { + await module.close(); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('getData', () => { + describe('when image url is provided', () => { + const setup = () => { + const currentUser = { userId: 'fakeUserId' } as ICurrentUser; + const ucResult = { + url: '', + title: 'example title', + } as MetaData; + uc.fetchMetaData.mockResolvedValue(ucResult); + return { currentUser }; + }; + + it('should call uc with two parentIds', async () => { + const { currentUser } = setup(); + + const url = 'https://super-duper-url.com'; + await controller.getData(currentUser, { url }); + + expect(uc.fetchMetaData).toHaveBeenCalledWith(currentUser.userId, url); + }); + }); + }); +}); From e8fb31b4523875501696b28e535abcc2958d7762 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 3 Nov 2023 12:15:22 +0100 Subject: [PATCH 08/12] add check if user is authorized --- .../meta-tag-extractor/uc/meta-tag-extractor.uc.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts index d7264f6d193..2952d256047 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts @@ -1,12 +1,13 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; import { LegacyLogger } from '@src/core/logger'; +import { AuthorizationService } from '@src/modules/authorization'; import { MetaData, MetaTagExtractorService } from '../service'; @Injectable() export class MetaTagExtractorUc { constructor( - // private readonly authorizationService: AuthorizationService, + private readonly authorizationService: AuthorizationService, private readonly metaTagExtractorService: MetaTagExtractorService, private readonly logger: LegacyLogger ) { @@ -16,9 +17,12 @@ export class MetaTagExtractorUc { async fetchMetaData(userId: EntityId, url: string): Promise { this.logger.debug({ action: 'fetchMetaData', userId }); - const result = await this.metaTagExtractorService.fetchMetaData(url); - // WIP: check permission + const user = await this.authorizationService.getUserWithPermissions(userId); + if (!user) { + throw new UnauthorizedException(); + } + const result = await this.metaTagExtractorService.fetchMetaData(url); return result; } } From 13e134bc2c4f0e555e5b90cbd3922193a4f38d75 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 3 Nov 2023 13:09:36 +0100 Subject: [PATCH 09/12] chore: add test for meta-tag-extractor.uc --- .../uc/meta-tag-extractor.uc.spec.ts | 96 +++++++++++++++++++ .../uc/meta-tag-extractor.uc.ts | 5 +- 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts new file mode 100644 index 00000000000..75564ef8f4c --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts @@ -0,0 +1,96 @@ +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { UnauthorizedException } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { setupEntities, userFactory } from '@shared/testing'; +import { LegacyLogger } from '@src/core/logger'; +import { AuthorizationService } from '@src/modules/authorization'; +import { MetaTagExtractorService } from '../service'; +import { MetaTagExtractorUc } from './meta-tag-extractor.uc'; + +describe(MetaTagExtractorUc.name, () => { + let module: TestingModule; + let uc: MetaTagExtractorUc; + let authorizationService: DeepMocked; + let metaTagExtractorService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + MetaTagExtractorUc, + { + provide: MetaTagExtractorService, + useValue: createMock(), + }, + { + provide: AuthorizationService, + useValue: createMock(), + }, + { + provide: LegacyLogger, + useValue: createMock(), + }, + ], + }).compile(); + + uc = module.get(MetaTagExtractorUc); + authorizationService = module.get(AuthorizationService); + metaTagExtractorService = module.get(MetaTagExtractorService); + + await setupEntities(); + }); + + afterAll(async () => { + await module.close(); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + describe('fetchMetaData', () => { + describe('when user exists', () => { + const setup = () => { + const user = userFactory.build(); + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + + return { user }; + }; + + it('should check if the user is a valid user', async () => { + const { user } = setup(); + + authorizationService.getUserWithPermissions.mockResolvedValueOnce(user); + + const url = 'https://www.example.com/great-example'; + await uc.fetchMetaData(user.id, url); + + expect(authorizationService.getUserWithPermissions).toHaveBeenCalledWith(user.id); + }); + + it('should call meta tag extractor service', async () => { + const { user } = setup(); + + const url = 'https://www.example.com/great-example'; + await uc.fetchMetaData(user.id, url); + + expect(metaTagExtractorService.fetchMetaData).toHaveBeenCalledWith(url); + }); + }); + + describe('when user does not exist', () => { + const setup = () => { + const user = userFactory.build(); + authorizationService.getUserWithPermissions.mockRejectedValue(false); + + return { user }; + }; + + it('should throw an UnauthorizedException', async () => { + const { user } = setup(); + + const url = 'https://www.example.com/great-example'; + await expect(uc.fetchMetaData(user.id, url)).rejects.toThrow(UnauthorizedException); + }); + }); + }); +}); diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts index 2952d256047..601172086d7 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts @@ -17,8 +17,9 @@ export class MetaTagExtractorUc { async fetchMetaData(userId: EntityId, url: string): Promise { this.logger.debug({ action: 'fetchMetaData', userId }); - const user = await this.authorizationService.getUserWithPermissions(userId); - if (!user) { + try { + await this.authorizationService.getUserWithPermissions(userId); + } catch (error) { throw new UnauthorizedException(); } From c5b7187a8bf43b2ed64e10263c8e283006083f01 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 3 Nov 2023 14:17:31 +0100 Subject: [PATCH 10/12] chore: remove unneeded logger --- .../uc/meta-tag-extractor.uc.spec.ts | 5 ----- .../meta-tag-extractor/uc/meta-tag-extractor.uc.ts | 10 ++-------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts index 75564ef8f4c..118b7d82633 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.spec.ts @@ -2,7 +2,6 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { UnauthorizedException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { setupEntities, userFactory } from '@shared/testing'; -import { LegacyLogger } from '@src/core/logger'; import { AuthorizationService } from '@src/modules/authorization'; import { MetaTagExtractorService } from '../service'; import { MetaTagExtractorUc } from './meta-tag-extractor.uc'; @@ -25,10 +24,6 @@ describe(MetaTagExtractorUc.name, () => { provide: AuthorizationService, useValue: createMock(), }, - { - provide: LegacyLogger, - useValue: createMock(), - }, ], }).compile(); diff --git a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts index 601172086d7..5daca6c962d 100644 --- a/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts +++ b/apps/server/src/modules/meta-tag-extractor/uc/meta-tag-extractor.uc.ts @@ -1,6 +1,5 @@ import { Injectable, UnauthorizedException } from '@nestjs/common'; import { EntityId } from '@shared/domain'; -import { LegacyLogger } from '@src/core/logger'; import { AuthorizationService } from '@src/modules/authorization'; import { MetaData, MetaTagExtractorService } from '../service'; @@ -8,15 +7,10 @@ import { MetaData, MetaTagExtractorService } from '../service'; export class MetaTagExtractorUc { constructor( private readonly authorizationService: AuthorizationService, - private readonly metaTagExtractorService: MetaTagExtractorService, - private readonly logger: LegacyLogger - ) { - this.logger.setContext(MetaTagExtractorUc.name); - } + private readonly metaTagExtractorService: MetaTagExtractorService + ) {} async fetchMetaData(userId: EntityId, url: string): Promise { - this.logger.debug({ action: 'fetchMetaData', userId }); - try { await this.authorizationService.getUserWithPermissions(userId); } catch (error) { From 066bdab5401fed35c595668cb660c0acd6dee4b2 Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 3 Nov 2023 15:42:08 +0100 Subject: [PATCH 11/12] WIP: api-test-implementation --- .../meta-tag-extractor-get-data.api.spec.ts | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts diff --git a/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts new file mode 100644 index 00000000000..44b12ee303e --- /dev/null +++ b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts @@ -0,0 +1,113 @@ +import { EntityManager } from '@mikro-orm/mongodb'; +import { ICurrentUser } from '@modules/authentication'; +import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; +import { ServerTestModule } from '@modules/server/server.module'; +import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ApiValidationError } from '@shared/common'; +import { cleanupCollections, courseFactory, mapUserToCurrentUser, userFactory } from '@shared/testing'; +import { Request } from 'express'; +import request from 'supertest'; +import { MetaTagExtractorService } from '../../service'; +import { MetaTagExtractorResponse } from '../dto'; + +const baseRouteName = '/meta-tag-extractor'; + +class API { + app: INestApplication; + + constructor(app: INestApplication) { + this.app = app; + } + + async post(columnId: string, requestBody?: object) { + const response = await request(this.app.getHttpServer()) + .post(`${baseRouteName}/${columnId}/cards`) + .set('Accept', 'application/json') + .send(requestBody); + + return { + result: response.body as MetaTagExtractorResponse, + error: response.body as ApiValidationError, + status: response.status, + }; + } +} + +describe(`get data (api)`, () => { + let app: INestApplication; + let em: EntityManager; + let currentUser: ICurrentUser; + let api: API; + // const metaTagExtractorServiceMock = createMock(); + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ServerTestModule], + }) + .overrideGuard(JwtAuthGuard) + .useValue({ + canActivate(context: ExecutionContext) { + const req: Request = context.switchToHttp().getRequest(); + req.user = currentUser; + return true; + }, + }) + .overrideProvider(MetaTagExtractorService) + .useValue({ + fetchMetaData: () => { + return { url: 'https://test.de', title: '', description: '' }; + }, + }) + .compile(); + + app = module.createNestApplication(); + await app.init(); + em = module.get(EntityManager); + api = new API(app); + }); + + afterAll(async () => { + await app.close(); + }); + + const setup = async () => { + await cleanupCollections(em); + const user = userFactory.build(); + const course = courseFactory.build({ teachers: [user] }); + await em.persistAndFlush([user, course]); + + em.clear(); + + return { user }; + }; + + // const URL = 'https://www.myexample.com/mocked-article-url'; + const URL = 'https://default-bc-5434-meta-data-endpoint.cd.dbildungscloud.dev/rooms-overview/'; + + describe('with valid user', () => { + it('should return status 201', async () => { + const { user } = await setup(); + currentUser = mapUserToCurrentUser(user); + + // metaTagExtractorServiceMock.fetchMetaData.mockResolvedValueOnce({ url: URL, title: '', description: '' }); + + const response = await api.post(URL); + + expect(response.status).toEqual(201); + }); + + it('should return the meta tags of the external page', async () => { + const { user } = await setup(); + currentUser = mapUserToCurrentUser(user); + + const { result } = await api.post(URL); + console.log(result); + expect(result.title).toBeDefined(); + }); + }); + + describe('with invalid user', () => { + it.skip('should return status 404', async () => {}); + }); +}); From 05741fdd0dff18f508981e73197495cbfba383bb Mon Sep 17 00:00:00 2001 From: hoeppner-dataport Date: Fri, 3 Nov 2023 16:19:58 +0100 Subject: [PATCH 12/12] add API-Test for new endpoint --- .../meta-tag-extractor-get-data.api.spec.ts | 98 ++++++------------- .../meta-tag-extractor.controller.spec.ts | 58 ----------- .../meta-tag-extractor.controller.ts | 18 +--- .../service/meta-tag-extractor.service.ts | 1 - 4 files changed, 35 insertions(+), 140 deletions(-) delete mode 100644 apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts diff --git a/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts index 44b12ee303e..c80d47df66d 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/api-test/meta-tag-extractor-get-data.api.spec.ts @@ -1,113 +1,77 @@ import { EntityManager } from '@mikro-orm/mongodb'; -import { ICurrentUser } from '@modules/authentication'; -import { JwtAuthGuard } from '@modules/authentication/guard/jwt-auth.guard'; import { ServerTestModule } from '@modules/server/server.module'; -import { ExecutionContext, INestApplication } from '@nestjs/common'; +import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ApiValidationError } from '@shared/common'; -import { cleanupCollections, courseFactory, mapUserToCurrentUser, userFactory } from '@shared/testing'; -import { Request } from 'express'; -import request from 'supertest'; +import { TestApiClient, UserAndAccountTestFactory } from '@shared/testing'; import { MetaTagExtractorService } from '../../service'; -import { MetaTagExtractorResponse } from '../dto'; -const baseRouteName = '/meta-tag-extractor'; +const URL = 'https://test.de'; -class API { - app: INestApplication; - - constructor(app: INestApplication) { - this.app = app; - } - - async post(columnId: string, requestBody?: object) { - const response = await request(this.app.getHttpServer()) - .post(`${baseRouteName}/${columnId}/cards`) - .set('Accept', 'application/json') - .send(requestBody); - - return { - result: response.body as MetaTagExtractorResponse, - error: response.body as ApiValidationError, - status: response.status, - }; - } -} +const mockedResponse = { + url: URL, + title: 'The greatest Test-Page', + description: 'with great description', +}; describe(`get data (api)`, () => { let app: INestApplication; let em: EntityManager; - let currentUser: ICurrentUser; - let api: API; - // const metaTagExtractorServiceMock = createMock(); + let testApiClient: TestApiClient; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ServerTestModule], }) - .overrideGuard(JwtAuthGuard) - .useValue({ - canActivate(context: ExecutionContext) { - const req: Request = context.switchToHttp().getRequest(); - req.user = currentUser; - return true; - }, - }) .overrideProvider(MetaTagExtractorService) .useValue({ - fetchMetaData: () => { - return { url: 'https://test.de', title: '', description: '' }; - }, + fetchMetaData: () => mockedResponse, }) .compile(); app = module.createNestApplication(); await app.init(); em = module.get(EntityManager); - api = new API(app); + testApiClient = new TestApiClient(app, '/meta-tag-extractor'); }); afterAll(async () => { await app.close(); }); - const setup = async () => { - await cleanupCollections(em); - const user = userFactory.build(); - const course = courseFactory.build({ teachers: [user] }); - await em.persistAndFlush([user, course]); + describe('with valid user', () => { + const setup = async () => { + const { teacherAccount, teacherUser } = UserAndAccountTestFactory.buildTeacher(); - em.clear(); + await em.persistAndFlush([teacherAccount, teacherUser]); + em.clear(); - return { user }; - }; + const loggedInClient = await testApiClient.login(teacherAccount); - // const URL = 'https://www.myexample.com/mocked-article-url'; - const URL = 'https://default-bc-5434-meta-data-endpoint.cd.dbildungscloud.dev/rooms-overview/'; + return { loggedInClient }; + }; - describe('with valid user', () => { it('should return status 201', async () => { - const { user } = await setup(); - currentUser = mapUserToCurrentUser(user); - - // metaTagExtractorServiceMock.fetchMetaData.mockResolvedValueOnce({ url: URL, title: '', description: '' }); + const { loggedInClient } = await setup(); - const response = await api.post(URL); + const { status } = await loggedInClient.post(undefined, { url: URL }); - expect(response.status).toEqual(201); + expect(status).toEqual(201); }); it('should return the meta tags of the external page', async () => { - const { user } = await setup(); - currentUser = mapUserToCurrentUser(user); + const { loggedInClient } = await setup(); - const { result } = await api.post(URL); - console.log(result); - expect(result.title).toBeDefined(); + const response = await loggedInClient.post(undefined, { url: URL }); + + expect(response?.body).toEqual(mockedResponse); }); }); describe('with invalid user', () => { - it.skip('should return status 404', async () => {}); + it('should return status 401', async () => { + const { status } = await testApiClient.post(undefined, { url: URL }); + + expect(status).toEqual(401); + }); }); }); diff --git a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts deleted file mode 100644 index 399c0261426..00000000000 --- a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.spec.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { createMock, DeepMocked } from '@golevelup/ts-jest'; -import { ICurrentUser } from '@modules/authentication'; -import { Test, TestingModule } from '@nestjs/testing'; -import { MetaData } from '../service'; -import { MetaTagExtractorUc } from '../uc'; -import { MetaTagExtractorController } from './meta-tag-extractor.controller'; - -describe('TaskController', () => { - let module: TestingModule; - let controller: MetaTagExtractorController; - let uc: DeepMocked; - - beforeAll(async () => { - module = await Test.createTestingModule({ - providers: [ - { - provide: MetaTagExtractorUc, - useValue: createMock(), - }, - ], - controllers: [MetaTagExtractorController], - }).compile(); - - controller = module.get(MetaTagExtractorController); - uc = module.get(MetaTagExtractorUc); - }); - - afterAll(async () => { - await module.close(); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); - - describe('getData', () => { - describe('when image url is provided', () => { - const setup = () => { - const currentUser = { userId: 'fakeUserId' } as ICurrentUser; - const ucResult = { - url: '', - title: 'example title', - } as MetaData; - uc.fetchMetaData.mockResolvedValue(ucResult); - return { currentUser }; - }; - - it('should call uc with two parentIds', async () => { - const { currentUser } = setup(); - - const url = 'https://super-duper-url.com'; - await controller.getData(currentUser, { url }); - - expect(uc.fetchMetaData).toHaveBeenCalledWith(currentUser.userId, url); - }); - }); - }); -}); diff --git a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts index e9a8df0434e..8133c4c0b83 100644 --- a/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts +++ b/apps/server/src/modules/meta-tag-extractor/controller/meta-tag-extractor.controller.ts @@ -1,13 +1,5 @@ -import { - BadRequestException, - Body, - Controller, - ForbiddenException, - InternalServerErrorException, - Post, -} from '@nestjs/common'; +import { Body, Controller, InternalServerErrorException, Post, UnauthorizedException } from '@nestjs/common'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { ApiValidationError } from '@shared/common'; import { ICurrentUser } from '@src/modules/authentication'; import { Authenticate, CurrentUser } from '@src/modules/authentication/decorator/auth.decorator'; import { MetaTagExtractorUc } from '../uc'; @@ -20,13 +12,11 @@ import { GetMetaTagDataBody } from './post-link-url.body.params'; export class MetaTagExtractorController { constructor(private readonly metaTagExtractorUc: MetaTagExtractorUc) {} - @ApiOperation({ summary: 'Return dummy HTML for testing' }) + @ApiOperation({ summary: 'return extract meta tags' }) @ApiResponse({ status: 201, type: MetaTagExtractorResponse }) - @ApiResponse({ status: 400, type: ApiValidationError }) - @ApiResponse({ status: 400, type: BadRequestException }) - @ApiResponse({ status: 403, type: ForbiddenException }) + @ApiResponse({ status: 401, type: UnauthorizedException }) @ApiResponse({ status: 500, type: InternalServerErrorException }) - @Post('/:url') + @Post('') async getData( @CurrentUser() currentUser: ICurrentUser, @Body() bodyParams: GetMetaTagDataBody diff --git a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts index f99959057ba..46c30c17702 100644 --- a/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts +++ b/apps/server/src/modules/meta-tag-extractor/service/meta-tag-extractor.service.ts @@ -14,7 +14,6 @@ export type MetaData = { export class MetaTagExtractorService { async fetchMetaData(url: string): Promise { if (url.length === 0) { - // WIP: add nice debug logging for available open GraphData?!? throw new Error(`MetaTagExtractorService requires a valid URL. Given URL: ${url}`); }