Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BC-5189 - link element #4444

Merged
merged 42 commits into from
Oct 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
644a4c8
initial commit
hoeppner-dataport Sep 27, 2023
8f45f02
chore: implement response mapping for link element
hoeppner-dataport Sep 27, 2023
0b1c514
chore: add test for buildLinkElement of board-do.builder
hoeppner-dataport Sep 27, 2023
74192ae
chore: add link element to anyContentBody definition
hoeppner-dataport Sep 27, 2023
a1a8114
chore: add tests for content-element-update.visitor
hoeppner-dataport Sep 27, 2023
cbf4777
chore: add test for recursive delete visitor
hoeppner-dataport Sep 27, 2023
ecea6c9
adds missing type for linkelementresponse
OliverHappe Sep 27, 2023
72d815c
Merge branch 'BC-5189-link-element' of github.com:hpi-schul-cloud/sch…
OliverHappe Sep 27, 2023
a90bb04
chore: fix api generation issue by using @ApiExtraModels
hoeppner-dataport Sep 27, 2023
d3e6c4a
Merge branch 'BC-5189-link-element' of github.com:hpi-schul-cloud/sch…
OliverHappe Sep 27, 2023
b9834c9
chore: add text for recursive-save.visitor
hoeppner-dataport Sep 27, 2023
d9dd370
chore: add test for linkElementDo
hoeppner-dataport Sep 27, 2023
ddb8c43
chore: add test for link-element-node.entity
hoeppner-dataport Sep 27, 2023
46dd4aa
chore: add test for link element response
hoeppner-dataport Sep 27, 2023
3b12a08
chore: add test for linkElementResponseFactory
hoeppner-dataport Sep 27, 2023
44b4783
Merge branch 'BC-5189-link-element' of github.com:hpi-schul-cloud/sch…
hoeppner-dataport Sep 27, 2023
9fecad3
Merge branch 'main' into BC-5189-link-element
hoeppner-dataport Sep 28, 2023
b3aa5bc
gather open graph data for linkElements
hoeppner-dataport Sep 28, 2023
06d5852
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
hoeppner-dataport Sep 28, 2023
bff9a03
trying to fix swagger
OliverHappe Sep 29, 2023
743ed53
chore: fix problem with api generator
hoeppner-dataport Sep 29, 2023
0825839
handle invalid urls in open-graph-proxy
OliverHappe Sep 29, 2023
7afd6bb
fix: update visitor for linkelements
hoeppner-dataport Sep 29, 2023
e456c00
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
hoeppner-dataport Oct 4, 2023
23b3ddf
refactoring of open data proxy usage (find => save)
hoeppner-dataport Oct 4, 2023
09b4f3c
chore: fix test
hoeppner-dataport Oct 5, 2023
ae98800
chore: fix test
hoeppner-dataport Oct 5, 2023
8bb1325
Merge branch 'main' into BC-5189-link-element
hoeppner-dataport Oct 6, 2023
0b6866a
Merge branch 'BC-5189-link-element' of github.com:hpi-schul-cloud/sch…
hoeppner-dataport Oct 6, 2023
1a0ece1
chore: remove console.log
hoeppner-dataport Oct 6, 2023
3fbfd18
chore: fix tests
hoeppner-dataport Oct 6, 2023
2e5c63d
add feature toogle to schemas
hoeppner-dataport Oct 6, 2023
1915269
chore: fix non related test (scout rule)
hoeppner-dataport Oct 6, 2023
6ca1c78
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
hoeppner-dataport Oct 6, 2023
5885b3c
chore: fix test status 204 => 201
hoeppner-dataport Oct 6, 2023
80b7c57
chore: added new feature toggle to config-endpoint
hoeppner-dataport Oct 9, 2023
fbf545d
conar: sonar fix - remove second empty line at eof
hoeppner-dataport Oct 9, 2023
a9b0ac7
chore: add test for open graph proxy service
hoeppner-dataport Oct 9, 2023
4750052
chore: add test copying link elements
hoeppner-dataport Oct 9, 2023
204b16e
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
hoeppner-dataport Oct 10, 2023
c7e8776
chore: add trailing comma
hoeppner-dataport Oct 10, 2023
8b1c48c
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
hoeppner-dataport Oct 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/server/src/modules/board/board.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
ColumnBoardService,
ColumnService,
ContentElementService,
OpenGraphProxyService,
SubmissionItemService,
} from './service';
import { BoardDoCopyService, SchoolSpecificFileCopyServiceFactory } from './service/board-do-copy-service';
Expand All @@ -37,6 +38,7 @@ import { ColumnBoardCopyService } from './service/column-board-copy.service';
BoardDoCopyService,
ColumnBoardCopyService,
SchoolSpecificFileCopyServiceFactory,
OpenGraphProxyService,
],
exports: [
BoardDoAuthorizableService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe(`card create (api)`, () => {
em.clear();

const createCardBodyParams = {
requiredEmptyElements: [ContentElementType.RICH_TEXT, ContentElementType.FILE],
requiredEmptyElements: [ContentElementType.RICH_TEXT, ContentElementType.FILE, ContentElementType.LINK],
};

return { user, columnBoardNode, columnNode, createCardBodyParams };
Expand All @@ -111,7 +111,7 @@ describe(`card create (api)`, () => {

expect(result.id).toBeDefined();
});
it('created card should contain empty text and file elements', async () => {
it('created card should contain empty text, file and link elements', async () => {
const { user, columnNode, createCardBodyParams } = await setup();
currentUser = mapUserToCurrentUser(user);

Expand All @@ -129,13 +129,20 @@ describe(`card create (api)`, () => {
alternativeText: '',
},
},
{
type: 'link',
content: {
url: '',
},
},
];

const { result } = await api.post(columnNode.id, createCardBodyParams);
const { elements } = result;

expect(elements[0]).toMatchObject(expectedEmptyElements[0]);
expect(elements[1]).toMatchObject(expectedEmptyElements[1]);
expect(elements[2]).toMatchObject(expectedEmptyElements[2]);
});
it('should return status 400 as the content element is unknown', async () => {
const { user, columnNode } = await setup();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe(`content element update content (api)`, () => {
};
};

it('should return status 204', async () => {
it('should return status 201', async () => {
const { loggedInClient, richTextElement } = await setup();

const response = await loggedInClient.patch(`${richTextElement.id}/content`, {
Expand All @@ -108,7 +108,7 @@ describe(`content element update content (api)`, () => {
},
});

expect(response.statusCode).toEqual(204);
expect(response.statusCode).toEqual(201);
});

it('should actually change content of the element', async () => {
Expand Down Expand Up @@ -167,7 +167,7 @@ describe(`content element update content (api)`, () => {
expect(result.alternativeText).toEqual('rich text 1 some more text');
});

it('should return status 204 (nothing changed) without dueDate parameter for submission container element', async () => {
it('should return status 201', async () => {
const { loggedInClient, submissionContainerElement } = await setup();
const response = await loggedInClient.patch(`${submissionContainerElement.id}/content`, {
data: {
Expand All @@ -176,7 +176,7 @@ describe(`content element update content (api)`, () => {
},
});

expect(response.statusCode).toEqual(204);
expect(response.statusCode).toEqual(201);
});

it('should not change dueDate when not proviced in submission container element without dueDate', async () => {
Expand Down
15 changes: 9 additions & 6 deletions apps/server/src/modules/board/controller/card.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
CreateContentElementBodyParams,
ExternalToolElementResponse,
FileElementResponse,
LinkElementResponse,
MoveCardBodyParams,
RenameBodyParams,
RichTextElementResponse,
Expand Down Expand Up @@ -116,19 +117,21 @@ export class CardController {

@ApiOperation({ summary: 'Create a new element on a card.' })
@ApiExtraModels(
RichTextElementResponse,
ExternalToolElementResponse,
FileElementResponse,
SubmissionContainerElementResponse,
ExternalToolElementResponse
LinkElementResponse,
RichTextElementResponse,
SubmissionContainerElementResponse
)
@ApiResponse({
status: 201,
schema: {
oneOf: [
{ $ref: getSchemaPath(RichTextElementResponse) },
{ $ref: getSchemaPath(ExternalToolElementResponse) },
{ $ref: getSchemaPath(FileElementResponse) },
{ $ref: getSchemaPath(LinkElementResponse) },
{ $ref: getSchemaPath(RichTextElementResponse) },
{ $ref: getSchemaPath(SubmissionContainerElementResponse) },
{ $ref: getSchemaPath(ExternalToolElementResponse) },
],
},
})
Expand All @@ -137,7 +140,7 @@ export class CardController {
@ApiResponse({ status: 404, type: NotFoundException })
@Post(':cardId/elements')
async createElement(
@Param() urlParams: CardUrlParams, // TODO add type-property ?
@Param() urlParams: CardUrlParams,
@Body() bodyParams: CreateContentElementBodyParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<AnyContentElementResponse> {
Expand Down
22 changes: 18 additions & 4 deletions apps/server/src/modules/board/controller/dto/card/card.response.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger';
import { DecodeHtmlEntities } from '@shared/controller';
import { AnyContentElementResponse, FileElementResponse, SubmissionContainerElementResponse } from '../element';
import { RichTextElementResponse } from '../element/rich-text-element.response';
import {
AnyContentElementResponse,
ExternalToolElementResponse,
FileElementResponse,
LinkElementResponse,
RichTextElementResponse,
SubmissionContainerElementResponse,
} from '../element';
import { TimestampsResponse } from '../timestamps.response';
import { VisibilitySettingsResponse } from './visibility-settings.response';

@ApiExtraModels(RichTextElementResponse)
@ApiExtraModels(
ExternalToolElementResponse,
FileElementResponse,
LinkElementResponse,
RichTextElementResponse,
SubmissionContainerElementResponse
)
export class CardResponse {
constructor({ id, title, height, elements, visibilitySettings, timestamps }: CardResponse) {
this.id = id;
Expand All @@ -32,8 +44,10 @@ export class CardResponse {
type: 'array',
items: {
oneOf: [
{ $ref: getSchemaPath(RichTextElementResponse) },
{ $ref: getSchemaPath(ExternalToolElementResponse) },
{ $ref: getSchemaPath(FileElementResponse) },
{ $ref: getSchemaPath(LinkElementResponse) },
{ $ref: getSchemaPath(RichTextElementResponse) },
{ $ref: getSchemaPath(SubmissionContainerElementResponse) },
],
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ExternalToolElementResponse } from './external-tool-element.response';
import { FileElementResponse } from './file-element.response';
import { LinkElementResponse } from './link-element.response';
import { RichTextElementResponse } from './rich-text-element.response';
import { SubmissionContainerElementResponse } from './submission-container-element.response';

export type AnyContentElementResponse =
| FileElementResponse
| LinkElementResponse
| RichTextElementResponse
| SubmissionContainerElementResponse
| ExternalToolElementResponse;
5 changes: 3 additions & 2 deletions apps/server/src/modules/board/controller/dto/element/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './any-content-element.response';
export * from './create-content-element.body.params';
export * from './update-element-content.body.params';
export * from './external-tool-element.response';
export * from './file-element.response';
export * from './link-element.response';
export * from './rich-text-element.response';
export * from './submission-container-element.response';
export * from './external-tool-element.response';
export * from './update-element-content.body.params';
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { ContentElementType } from '@shared/domain';
import { TimestampsResponse } from '../timestamps.response';

export class LinkElementContent {
constructor({ url, title, description, imageUrl }: LinkElementContent) {
this.url = url;
this.title = title;
this.description = description;
this.imageUrl = imageUrl;
}

@ApiProperty()
url: string;

@ApiProperty()
title: string;

@ApiPropertyOptional()
description?: string;

@ApiPropertyOptional()
imageUrl?: string;
}

export class LinkElementResponse {
constructor({ id, content, timestamps, type }: LinkElementResponse) {
this.id = id;
this.content = content;
this.timestamps = timestamps;
this.type = type;
}

@ApiProperty({ pattern: '[a-f0-9]{24}' })
id: string;

@ApiProperty({ enum: ContentElementType, enumName: 'ContentElementType' })
type: ContentElementType.LINK;

@ApiProperty()
content: LinkElementContent;

@ApiProperty()
timestamps: TimestampsResponse;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@ export class FileElementContentBody extends ElementContentBody {
@ApiProperty()
content!: FileContentBody;
}
export class LinkContentBody {
@IsString()
@ApiProperty({})
url!: string;
}

export class LinkElementContentBody extends ElementContentBody {
@ApiProperty({ type: ContentElementType.LINK })
type!: ContentElementType.LINK;

@ValidateNested()
@ApiProperty({})
content!: LinkContentBody;
}

export class RichTextContentBody {
@IsString()
Expand Down Expand Up @@ -89,6 +103,7 @@ export class ExternalToolElementContentBody extends ElementContentBody {

export type AnyElementContentBody =
| FileContentBody
| LinkContentBody
| RichTextContentBody
| SubmissionContainerContentBody
| ExternalToolContentBody;
Expand All @@ -100,6 +115,7 @@ export class UpdateElementContentBodyParams {
property: 'type',
subTypes: [
{ value: FileElementContentBody, name: ContentElementType.FILE },
{ value: LinkElementContentBody, name: ContentElementType.LINK },
{ value: RichTextElementContentBody, name: ContentElementType.RICH_TEXT },
{ value: SubmissionContainerElementContentBody, name: ContentElementType.SUBMISSION_CONTAINER },
{ value: ExternalToolElementContentBody, name: ContentElementType.EXTERNAL_TOOL },
Expand All @@ -110,13 +126,15 @@ export class UpdateElementContentBodyParams {
@ApiProperty({
oneOf: [
{ $ref: getSchemaPath(FileElementContentBody) },
{ $ref: getSchemaPath(LinkElementContentBody) },
{ $ref: getSchemaPath(RichTextElementContentBody) },
{ $ref: getSchemaPath(SubmissionContainerElementContentBody) },
{ $ref: getSchemaPath(ExternalToolElementContentBody) },
],
})
data!:
| FileElementContentBody
| LinkElementContentBody
| RichTextElementContentBody
| SubmissionContainerElementContentBody
| ExternalToolElementContentBody;
Expand Down
39 changes: 32 additions & 7 deletions apps/server/src/modules/board/controller/element.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,31 @@ import {
Post,
Put,
} from '@nestjs/common';
import { ApiBody, ApiExtraModels, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiExtraModels, ApiOperation, ApiResponse, ApiTags, getSchemaPath } 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 { CardUc } from '../uc';
import { ElementUc } from '../uc/element.uc';
import {
AnyContentElementResponse,
ContentElementUrlParams,
CreateSubmissionItemBodyParams,
ExternalToolElementContentBody,
ExternalToolElementResponse,
FileElementContentBody,
FileElementResponse,
LinkElementContentBody,
LinkElementResponse,
MoveContentElementBody,
RichTextElementContentBody,
RichTextElementResponse,
SubmissionContainerElementContentBody,
SubmissionContainerElementResponse,
SubmissionItemResponse,
UpdateElementContentBodyParams,
} from './dto';
import { SubmissionItemResponseMapper } from './mapper';
import { ContentElementResponseFactory, SubmissionItemResponseMapper } from './mapper';

@ApiTags('Board Element')
@Authenticate('jwt')
Expand Down Expand Up @@ -60,20 +67,38 @@ export class ElementController {
FileElementContentBody,
RichTextElementContentBody,
SubmissionContainerElementContentBody,
ExternalToolElementContentBody
ExternalToolElementContentBody,
LinkElementContentBody
)
@ApiResponse({ status: 204 })
@ApiResponse({
status: 201,
schema: {
oneOf: [
{ $ref: getSchemaPath(ExternalToolElementResponse) },
{ $ref: getSchemaPath(FileElementResponse) },
{ $ref: getSchemaPath(LinkElementResponse) },
{ $ref: getSchemaPath(RichTextElementResponse) },
{ $ref: getSchemaPath(SubmissionContainerElementResponse) },
],
},
})
@ApiResponse({ status: 400, type: ApiValidationError })
@ApiResponse({ status: 403, type: ForbiddenException })
@ApiResponse({ status: 404, type: NotFoundException })
@HttpCode(204)
@HttpCode(201)
@Patch(':contentElementId/content')
async updateElement(
@Param() urlParams: ContentElementUrlParams,
@Body() bodyParams: UpdateElementContentBodyParams,
@CurrentUser() currentUser: ICurrentUser
): Promise<void> {
await this.elementUc.updateElementContent(currentUser.userId, urlParams.contentElementId, bodyParams.data.content);
): Promise<AnyContentElementResponse> {
const element = await this.elementUc.updateElementContent(
currentUser.userId,
urlParams.contentElementId,
bodyParams.data.content
);
const response = ContentElementResponseFactory.mapToResponse(element);
return response;
}

@ApiOperation({ summary: 'Delete a single content element.' })
Expand Down
Loading
Loading