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-8348 - Implement Sharing and Copying of Room Boards #5338

Closed
wants to merge 71 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
a6db175
add schoolId to room
uidp Nov 11, 2024
b320a19
rename room roles - remove underscore
uidp Nov 11, 2024
b290e4a
fix test
uidp Nov 12, 2024
6198f50
rename school field
uidp Nov 12, 2024
834a059
fix entity fields
uidp Nov 12, 2024
4066cd5
update migrations seed
uidp Nov 12, 2024
902d21e
add ROOM_CREATE and ROOM_DELETE permissions
uidp Nov 13, 2024
0c9b430
add migration to infer room school from room memberships
uidp Nov 13, 2024
7c47ef7
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Nov 14, 2024
22a2224
update seeds
uidp Nov 14, 2024
f5340a5
extend api to return room permissions
uidp Nov 14, 2024
fa3d01e
refactor board copy to using room storage id
uidp Nov 15, 2024
5dc2227
remove comment
uidp Nov 15, 2024
adfe30a
Merge branch 'main' into BC-8348-copy-share-board
uidp Nov 15, 2024
a50cfd0
simplify destination reference for sahre token import
uidp Nov 17, 2024
ebb15e2
Merge branch 'BC-8348-copy-share-board' of github.com:hpi-schul-cloud…
uidp Nov 17, 2024
c776afa
refactor share import destination
uidp Nov 19, 2024
b430522
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Nov 19, 2024
c26af42
rename room role enum keys
uidp Nov 19, 2024
c716fdd
add missing module import
uidp Nov 19, 2024
b26f9e1
adapt share token create and lookup auth
uidp Nov 25, 2024
eb7ba3e
fix unit test
uidp Nov 25, 2024
f7080d2
fix tests
uidp Nov 25, 2024
0a0783a
adapt token lookup permission
uidp Nov 25, 2024
d8fb524
fix room tests
uidp Nov 25, 2024
7f6221e
fix board tests
uidp Nov 25, 2024
76c3843
fix board tests
uidp Nov 25, 2024
bd25687
fix board tests
uidp Nov 25, 2024
0dbe7cc
fix board tests
uidp Nov 25, 2024
4d602f0
fix board tests
uidp Nov 25, 2024
7543526
fix board tests
uidp Nov 26, 2024
62d3b00
fix board tests
uidp Nov 26, 2024
7465d5a
fix board tests
uidp Nov 26, 2024
97612fc
fix board tests
uidp Nov 26, 2024
cf6c434
fix board tests
uidp Nov 26, 2024
871e8e4
fix board tests
uidp Nov 26, 2024
ffbb39c
fix board tests
uidp Nov 26, 2024
871e37b
fix board tests
uidp Nov 26, 2024
15e59c6
fix board tests
uidp Nov 26, 2024
02e17b2
Merge branch 'main' into BC-8348-copy-share-board
uidp Nov 26, 2024
574ed91
fix board tests
uidp Nov 26, 2024
a798db6
Merge branch 'BC-8348-copy-share-board' of github.com:hpi-schul-cloud…
uidp Nov 26, 2024
ea66c6f
fix room-member tests
uidp Nov 26, 2024
285a465
fix board-copy-service.spec
uidp Nov 27, 2024
aec3e57
fix dependency cycle
uidp Nov 27, 2024
fc6fdd9
reset sharing test changes
uidp Nov 27, 2024
a8507c3
reset room test changes
uidp Nov 27, 2024
62d7bc2
reset room-member test changes
uidp Nov 27, 2024
df9704d
reset board test changes
uidp Nov 27, 2024
aed45b4
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Nov 27, 2024
6896568
implement draft status on board copy
uidp Nov 27, 2024
8b9feea
rename room-member to room-membership
uidp Nov 27, 2024
9fb359d
fix linter errors
uidp Nov 28, 2024
84a0158
rename variables regarding roomMembership
uidp Nov 28, 2024
c887a77
add schoolId to roomMembership
uidp Nov 28, 2024
c23014b
add api test
uidp Nov 28, 2024
dc66818
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Nov 28, 2024
82f2924
fix migration seeds
uidp Nov 28, 2024
7d4f69a
undo rename migration
uidp Dec 2, 2024
939ae36
undo rename migration
uidp Dec 2, 2024
2437608
rename comments
uidp Dec 2, 2024
cd2a197
rename comments and console outputs
uidp Dec 2, 2024
097b117
fix module imports
uidp Dec 2, 2024
82c4c30
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Dec 2, 2024
3993226
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Dec 2, 2024
8f5e6cd
implement handling optional date values
uidp Dec 3, 2024
4a303ed
Merge branch 'main' of github.com:hpi-schul-cloud/schulcloud-server i…
uidp Dec 3, 2024
0b47cd3
add api tests
uidp Dec 3, 2024
09eee1b
fix tests and linting
uidp Dec 3, 2024
ec854b1
fix test coverage
uidp Dec 3, 2024
1d6d7fa
add api tests
uidp Dec 3, 2024
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
27 changes: 27 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration202411111604124.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241111160412 extends Migration {
async up(): Promise<void> {
// Rename ROOM_VIEWER role from room_viewer to roomviewer
await this.getCollection('roles').updateMany({ name: 'room_viewer' }, { $set: { name: 'roomviewer' } });

console.info('Renamed ROOM_VIEWER role from room_viewer to roomviewer');

// Rename ROOM_EDITOR role from room_editor to roomeditor
await this.getCollection('roles').updateMany({ name: 'room_editor' }, { $set: { name: 'roomeditor' } });

console.info('Renamed ROOM_EDITOR role from room_editor to roomeditor');
}

async down(): Promise<void> {
// Rename ROOM_VIEWER role from roomviewer to room_viewer
await this.getCollection('roles').updateMany({ name: 'roomviewer' }, { $set: { name: 'room_viewer' } });

console.info('Rollback: Renamed ROOM_VIEWER role from roomviewer to room_viewer');

// Rename ROOM_EDITOR role from roomeditor to room_editor
await this.getCollection('roles').updateMany({ name: 'roomeditor' }, { $set: { name: 'room_editor' } });

console.info('Rollback: Renamed ROOM_EDITOR role from roomeditor to room_editor');
}
}
31 changes: 31 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration202411121635382.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241112163538 extends Migration {
async up(): Promise<void> {
const collection = this.getCollection('room-members');

await collection.updateMany({ roomId: { $type: 'string' } }, [
{
$set: {
roomId: {
$convert: {
input: '$roomId',
to: 'objectId',
onError: '$roomId', // Keep the original value if conversion fails
onNull: '$roomId', // Keep the original value if the input is null
},
},
},
},
]);
console.info('Converted roomId from string to ObjectId');

await collection.updateMany({}, { $rename: { roomId: 'room' } });
console.info('Renamed roomId to room');
}

async down(): Promise<void> {
await Promise.resolve();
console.error(`Migration down not implemented. You might need to restore database from backup!`);
}
}
67 changes: 67 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration202411131005352.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241113100535 extends Migration {
async up(): Promise<void> {
const teacherRoleUpdate = await this.getCollection('roles').updateOne(
{ name: 'teacher' },
{
$addToSet: {
permissions: {
$each: ['ROOM_CREATE'],
},
},
}
);

if (teacherRoleUpdate.modifiedCount > 0) {
console.info('Permissions ROOM_CREATE added to role teacher.');
}

const roomEditorRoleUpdate = await this.getCollection('roles').updateOne(
{ name: 'roomeditor' },
{
$addToSet: {
permissions: {
$each: ['ROOM_DELETE'],
},
},
}
);

if (roomEditorRoleUpdate.modifiedCount > 0) {
console.info('Permissions ROOM_DELETE added to role roomeditor.');
}
}

async down(): Promise<void> {
const teacherRoleUpdate = await this.getCollection('roles').updateOne(
{ name: 'teacher' },
{
$pull: {
permissions: {
$in: ['ROOM_CREATE'],
},
},
}
);

if (teacherRoleUpdate.modifiedCount > 0) {
console.info('Rollback: Permission ROOM_CREATE added to role teacher.');
}

const roomEditorRoleUpdate = await this.getCollection('roles').updateOne(

Check failure on line 52 in apps/server/src/migrations/mikro-orm/Migration202411131005352.ts

View workflow job for this annotation

GitHub Actions / nest_lint

'roomEditorRoleUpdate' is assigned a value but never used
{ name: 'roomeditor' },
{
$pull: {
permissions: {
$in: ['ROOM_DELETE'],
},
},
}
);

if (teacherRoleUpdate.modifiedCount > 0) {
console.info('Rollback: Permission ROOM_DELETE added to role roomeditor.');
}
}
}
102 changes: 102 additions & 0 deletions apps/server/src/migrations/mikro-orm/Migration202411131520015.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { Migration } from '@mikro-orm/migrations-mongodb';

export class Migration20241113152001 extends Migration {
async up(): Promise<void> {
const roomsToSchoolView = [
{
$lookup: {
from: 'rooms',
localField: 'room',
foreignField: '_id',
as: 'roomDetails',
},
},
{
$unwind: '$roomDetails',
},
{
$match: {
'roomDetails.school': { $exists: false, $eq: null },
},
},
{
$lookup: {
from: 'groups',
localField: 'userGroup',
foreignField: '_id',
as: 'groupDetails',
},
},
{
$unwind: '$groupDetails',
},
{
$unwind: '$groupDetails.users',
},
{
$lookup: {
from: 'roles',
localField: 'groupDetails.users.role',
foreignField: '_id',
as: 'roleDetails',
},
},
{
$unwind: '$roleDetails',
},
{
$match: {
'roleDetails.name': 'roomeditor',
},
},
{
$lookup: {
from: 'users',
localField: 'groupDetails.users.user',
foreignField: '_id',
as: 'userDetails',
},
},
{
$unwind: '$userDetails',
},
{
$group: {
_id: '$userDetails.schoolId',
rooms: { $push: '$roomDetails._id' },
},
},
{
$project: {
_id: 0,
school: '$_id',
rooms: 1,
},
},
];

const mappings = await this.driver.aggregate('room-members', roomsToSchoolView);

for await (const mapping of mappings) {
const schoolUpdate = await this.driver.nativeUpdate(
'rooms',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
{ _id: { $in: mapping.rooms } },
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-argument,@typescript-eslint/no-unsafe-member-access
{ $set: { school: mapping.school } }
);

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access, @typescript-eslint/restrict-template-expressions, @typescript-eslint/no-unsafe-call
console.info(`Updated ${schoolUpdate.affectedRows} rooms with school ${mapping.school.toHexString()}`);
}

if (mappings.length === 0) {
console.info(`No rooms without school to update`);
}
}

async down(): Promise<void> {
await Promise.resolve();
console.error(`Migration down not implemented. You might need to restore database from backup!`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,11 @@ export interface RoomMemberEntityProps extends AuthorizableObject {
@Entity({ tableName: 'room-members' })
@Unique({ properties: ['roomId', 'userGroupId'] })
export class RoomMemberEntity extends BaseEntityWithTimestamps implements RoomMemberEntityProps {
@Property()
@Unique()
@Property({ type: ObjectIdType })
@Property({ type: ObjectIdType, fieldName: 'room' })
roomId!: EntityId;

@Property({ type: ObjectIdType })
@Property({ type: ObjectIdType, fieldName: 'userGroup' })
userGroupId!: EntityId;

@Property({ persist: false })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { RoomCreateProps } from '@src/modules/room/domain';
import { RoomColor } from '@src/modules/room/domain/type';
import { IsDate, IsEnum, IsOptional, IsString, MaxLength, MinLength } from 'class-validator';

export class CreateRoomBodyParams implements RoomCreateProps {
export class CreateRoomBodyParams implements Omit<RoomCreateProps, 'schoolId'> {
@ApiProperty({
description: 'The name of the room',
required: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export class RoomDetailsResponse {
@IsEnum(RoomColor)
color: RoomColor;

@ApiProperty()
schoolId: string;

@ApiPropertyOptional({ type: Date })
startDate?: Date;

Expand All @@ -29,6 +32,7 @@ export class RoomDetailsResponse {
this.id = room.id;
this.name = room.name;
this.color = room.color;
this.schoolId = room.schoolId;

this.startDate = room.startDate;
this.endDate = room.endDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export class RoomItemResponse {
@IsEnum(RoomColor)
color: RoomColor;

@ApiProperty()
schoolId: string;

@ApiPropertyOptional({ type: Date })
startDate?: Date;

Expand All @@ -29,6 +32,7 @@ export class RoomItemResponse {
this.id = room.id;
this.name = room.name;
this.color = room.color;
this.schoolId = room.schoolId;

this.startDate = room.startDate;
this.endDate = room.endDate;
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/modules/room/api/mapper/room.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class RoomMapper {
id: room.id,
name: room.name,
color: room.color,
schoolId: room.schoolId,
startDate: room.startDate,
endDate: room.endDate,
createdAt: room.createdAt,
Expand All @@ -37,6 +38,7 @@ export class RoomMapper {
id: room.id,
name: room.name,
color: room.color,
schoolId: room.schoolId,
startDate: room.startDate,
endDate: room.endDate,
createdAt: room.createdAt,
Expand Down
25 changes: 16 additions & 9 deletions apps/server/src/modules/room/api/room.uc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { Page, UserDO } from '@shared/domain/domainobject';
import { IFindOptions, Permission, RoleName, RoomRole } from '@shared/domain/interface';
import { EntityId } from '@shared/domain/types';
import { BoardExternalReferenceType, ColumnBoard, ColumnBoardService } from '@src/modules/board';
import { Room, RoomCreateProps, RoomService, RoomUpdateProps } from '../domain';
import { Room, RoomService } from '../domain';
import { RoomConfig } from '../room.config';
import { CreateRoomBodyParams } from './dto/request/create-room.body.params';
import { UpdateRoomBodyParams } from './dto/request/update-room.body.params';
import { RoomMemberResponse } from './dto/response/room-member.response';

@Injectable()
Expand All @@ -31,13 +33,13 @@ export class RoomUc {
return rooms;
}

public async createRoom(userId: EntityId, props: RoomCreateProps): Promise<Room> {
public async createRoom(userId: EntityId, props: CreateRoomBodyParams): Promise<Room> {
this.checkFeatureEnabled();

const user = await this.authorizationService.getUserWithPermissions(userId);
const room = await this.roomService.createRoom(props);
// NOTE: currently only teacher are allowed to create rooms. Could not find simpler way to check this.
this.authorizationService.checkOneOfPermissions(user, [Permission.COURSE_CREATE]);
const room = await this.roomService.createRoom({ ...props, schoolId: user.school.id });
// NOTE: currently only teachers are allowed to create rooms. Could not find simpler way to check this.
uidp marked this conversation as resolved.
Show resolved Hide resolved
this.authorizationService.checkOneOfPermissions(user, [Permission.ROOM_CREATE]);
await this.roomMemberService
.addMembersToRoom(room.id, [{ userId: user.id, roleName: RoleName.ROOM_EDITOR }], user.school.id)
.catch(async (err) => {
Expand Down Expand Up @@ -72,7 +74,7 @@ export class RoomUc {
return boards;
}

public async updateRoom(userId: EntityId, roomId: EntityId, props: RoomUpdateProps): Promise<Room> {
public async updateRoom(userId: EntityId, roomId: EntityId, props: UpdateRoomBodyParams): Promise<Room> {
this.checkFeatureEnabled();
const room = await this.roomService.getSingleRoom(roomId);

Expand All @@ -86,7 +88,7 @@ export class RoomUc {
this.checkFeatureEnabled();
const room = await this.roomService.getSingleRoom(roomId);

await this.checkRoomAuthorization(userId, roomId, Action.write);
await this.checkRoomAuthorization(userId, roomId, Action.write, [Permission.ROOM_DELETE]);
await this.roomService.deleteRoom(room);
}

Expand Down Expand Up @@ -151,10 +153,15 @@ export class RoomUc {
return authorizedRoomIds.map((item) => item.roomId);
}

private async checkRoomAuthorization(userId: EntityId, roomId: EntityId, action: Action): Promise<void> {
private async checkRoomAuthorization(
userId: EntityId,
roomId: EntityId,
action: Action,
requiredPermissions: Permission[] = []
): Promise<void> {
const roomMemberAuthorizable = await this.roomMemberService.getRoomMemberAuthorizable(roomId);
const user = await this.authorizationService.getUserWithPermissions(userId);
this.authorizationService.checkPermission(user, roomMemberAuthorizable, { action, requiredPermissions: [] });
this.authorizationService.checkPermission(user, roomMemberAuthorizable, { action, requiredPermissions });
}

private checkFeatureEnabled(): void {
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/modules/room/api/test/room-get.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ describe('Room Controller (API)', () => {
id: room.id,
name: room.name,
color: room.color,
schoolId: room.schoolId,
startDate: room.startDate?.toISOString(),
endDate: room.endDate?.toISOString(),
createdAt: room.createdAt.toISOString(),
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/modules/room/api/test/room-index.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe('Room Controller (API)', () => {
id: room.id,
name: room.name,
color: room.color,
schoolId: room.schoolId,
startDate: room.startDate?.toISOString(),
endDate: room.endDate?.toISOString(),
createdAt: room.createdAt.toISOString(),
Expand Down Expand Up @@ -152,6 +153,7 @@ describe('Room Controller (API)', () => {
id: room.id,
name: room.name,
color: room.color,
schoolId: room.schoolId,
startDate: room.startDate?.toISOString(),
endDate: room.endDate?.toISOString(),
createdAt: room.createdAt.toISOString(),
Expand Down
Loading
Loading