From c57daf62aa2022ec76a78aedad73be6424200d9c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Oct 2024 16:34:35 +0200 Subject: [PATCH 01/11] We need a temporary unique identifier for meetings to invite unauthorisized users to meetings. So far we used the meeting id, but these means, that any user may join the meeting by guessing the id. --- .../migration.sql | 11 ++++++ backend/prisma/schema.prisma | 1 + .../graphql/resolvers/TableResolver.spec.ts | 9 +++-- .../src/graphql/resolvers/TableResolver.ts | 34 +++++++++++++++---- .../graphql/resolvers/dal/handleOpenTables.ts | 1 + 5 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql diff --git a/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql b/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql new file mode 100644 index 000000000..eadeb194c --- /dev/null +++ b/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[temporaryID]` on the table `Meeting` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE `Meeting` ADD COLUMN `temporaryID` VARCHAR(36) NULL; + +-- CreateIndex +CREATE UNIQUE INDEX `Meeting_temporaryID_key` ON `Meeting`(`temporaryID`); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 27a896de1..2bf5c039e 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -79,6 +79,7 @@ model Meeting { id Int @id @default(autoincrement()) name String @db.VarChar(254) meetingID String @db.VarChar(36) @unique + temporaryID String? @db.VarChar(36) @unique attendeePW String? @db.VarChar(64) moderatorPW String? @db.VarChar(64) createdAt DateTime @default(now()) diff --git a/backend/src/graphql/resolvers/TableResolver.spec.ts b/backend/src/graphql/resolvers/TableResolver.spec.ts index 299151cb0..ffcf4ea48 100644 --- a/backend/src/graphql/resolvers/TableResolver.spec.ts +++ b/backend/src/graphql/resolvers/TableResolver.spec.ts @@ -337,7 +337,7 @@ describe('TableResolver', () => { describe('joinTableAsGuest', () => { const query = ` - query ($tableId: Int!, $userName: String!) { + query ($tableId: String!, $userName: String!) { joinTableAsGuest(tableId: $tableId, userName: $userName) } ` @@ -349,7 +349,7 @@ describe('TableResolver', () => { query, variables: { userName: 'Pinky Pie', - tableId: 25, + tableId: '25', }, }, { contextValue: mockContextValue() }, @@ -374,6 +374,7 @@ describe('TableResolver', () => { data: { name: 'Pony Ville', meetingID: 'Pony Ville', + temporaryID: 'temp-id', }, }) tableId = meeting.id @@ -390,7 +391,7 @@ describe('TableResolver', () => { query, variables: { userName: 'Pinky Pie', - tableId, + tableId: 'temp-id', }, }, { contextValue: mockContextValue() }, @@ -1223,6 +1224,8 @@ describe('TableResolver', () => { name: 'My Table', // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment meetingID: expect.any(String), + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + temporaryID: expect.any(String), attendeePW: 'w3VUvMcp', moderatorPW: 'MyPp9Zfq', voiceBridge: 255, diff --git a/backend/src/graphql/resolvers/TableResolver.ts b/backend/src/graphql/resolvers/TableResolver.ts index 234cd0895..535ac2fcf 100644 --- a/backend/src/graphql/resolvers/TableResolver.ts +++ b/backend/src/graphql/resolvers/TableResolver.ts @@ -189,7 +189,7 @@ export class TableResolver { if (!dbMeeting) throw new Error('Meeting not found!') - const inviteLink = createInviteLink(dbMeeting.id) + const inviteLink = await createInviteLink(prisma)(dbMeeting.id) await createBBBMeeting(prisma)({ meetingID: dbMeeting.meetingID, @@ -307,7 +307,7 @@ export class TableResolver { (table.user && table.user.id === user.id) || table.users.some((e) => e.userId === user.id && e.role === 'MODERATOR') ) { - const inviteLink = createInviteLink(table.id) + const inviteLink = await createInviteLink(prisma)(table.id) const meeting = await createBBBMeeting(prisma)({ meetingID: table.meetingID, name: table.name, @@ -345,7 +345,7 @@ export class TableResolver { @Query(() => String) async joinTableAsGuest( @Arg('userName') userName: string, - @Arg('tableId', () => Int) tableId: number, + @Arg('tableId', () => String) tableId: string, @Ctx() context: AuthenticatedContext, ): Promise { const { @@ -353,7 +353,7 @@ export class TableResolver { } = context const meeting = await prisma.meeting.findUnique({ where: { - id: tableId, + temporaryID: tableId, }, include: { user: true, @@ -657,9 +657,29 @@ const createMeetingID = (prisma: PrismaClient) => async (): Promise => { return meetingID } -function createInviteLink(tableId: number) { - return new URL(`join-table/${tableId}`, CONFIG.FRONTEND_URL).toString() -} +const createInviteLink = + (prisma: PrismaClient) => + async (tableId: number): Promise => { + let temporaryID: string = uuidv4() + while ( + await prisma.meeting.count({ + where: { + temporaryID, + }, + }) + ) { + temporaryID = uuidv4() + } + await prisma.meeting.update({ + where: { + id: tableId, + }, + data: { + temporaryID, + }, + }) + return new URL(`join-table/${temporaryID}`, CONFIG.FRONTEND_URL).toString() + } const createBBBMeeting = (prisma: PrismaClient) => diff --git a/backend/src/graphql/resolvers/dal/handleOpenTables.ts b/backend/src/graphql/resolvers/dal/handleOpenTables.ts index e19e5de97..b8e19e4ee 100644 --- a/backend/src/graphql/resolvers/dal/handleOpenTables.ts +++ b/backend/src/graphql/resolvers/dal/handleOpenTables.ts @@ -15,6 +15,7 @@ export const handleOpenTables = async (): Promise => { }, }, data: { + temporaryID: null, attendeePW: null, moderatorPW: null, voiceBridge: null, From 8560041cbf95d69d12d964f621a09c6dc045bbb1 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Mon, 14 Oct 2024 16:48:45 +0200 Subject: [PATCH 02/11] get table name queries DB by temporary ID and is therefore a string --- backend/src/graphql/resolvers/TableResolver.spec.ts | 11 +++++------ backend/src/graphql/resolvers/TableResolver.ts | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/backend/src/graphql/resolvers/TableResolver.spec.ts b/backend/src/graphql/resolvers/TableResolver.spec.ts index ffcf4ea48..045fe1ee3 100644 --- a/backend/src/graphql/resolvers/TableResolver.spec.ts +++ b/backend/src/graphql/resolvers/TableResolver.spec.ts @@ -430,7 +430,7 @@ describe('TableResolver', () => { describe('getTableName', () => { const query = ` - query ($tableId: Int!) { + query ($tableId: String!) { getTableName(tableId: $tableId) } ` @@ -441,7 +441,7 @@ describe('TableResolver', () => { { query, variables: { - tableId: 25, + tableId: '25', }, }, { contextValue: mockContextValue() }, @@ -459,15 +459,14 @@ describe('TableResolver', () => { }) describe('table in DB', () => { - let tableId: number beforeEach(async () => { - const meeting = await prisma.meeting.create({ + await prisma.meeting.create({ data: { name: 'Club of Rome', meetingID: 'Club of Rome', + temporaryID: 'club-of-rome', }, }) - tableId = meeting.id }) afterEach(async () => { @@ -480,7 +479,7 @@ describe('TableResolver', () => { { query, variables: { - tableId, + tableId: 'club-of-rome', }, }, { contextValue: mockContextValue() }, diff --git a/backend/src/graphql/resolvers/TableResolver.ts b/backend/src/graphql/resolvers/TableResolver.ts index 535ac2fcf..33f7dfca8 100644 --- a/backend/src/graphql/resolvers/TableResolver.ts +++ b/backend/src/graphql/resolvers/TableResolver.ts @@ -370,7 +370,7 @@ export class TableResolver { @Query(() => String) async getTableName( - @Arg('tableId', () => Int) tableId: number, + @Arg('tableId', () => String) tableId: string, @Ctx() context: AuthenticatedContext, ): Promise { const { @@ -378,7 +378,7 @@ export class TableResolver { } = context const meeting = await prisma.meeting.findUnique({ where: { - id: tableId, + temporaryID: tableId, }, }) if (!meeting) throw new Error('Table does not exist') From 16b8e457beaa8d724cfe729961a02fca3832fdcd Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 12:43:16 +0200 Subject: [PATCH 03/11] remove migration that is not needed --- .../migration.sql | 11 ----------- backend/prisma/schema.prisma | 1 - 2 files changed, 12 deletions(-) delete mode 100644 backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql diff --git a/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql b/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql deleted file mode 100644 index eadeb194c..000000000 --- a/backend/prisma/migrations/20241014140602_add_temporary_meeting_id/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[temporaryID]` on the table `Meeting` will be added. If there are existing duplicate values, this will fail. - -*/ --- AlterTable -ALTER TABLE `Meeting` ADD COLUMN `temporaryID` VARCHAR(36) NULL; - --- CreateIndex -CREATE UNIQUE INDEX `Meeting_temporaryID_key` ON `Meeting`(`temporaryID`); diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 2bf5c039e..27a896de1 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -79,7 +79,6 @@ model Meeting { id Int @id @default(autoincrement()) name String @db.VarChar(254) meetingID String @db.VarChar(36) @unique - temporaryID String? @db.VarChar(36) @unique attendeePW String? @db.VarChar(64) moderatorPW String? @db.VarChar(64) createdAt DateTime @default(now()) From cae76328723de6c3b8e021bbc00bcafb44826d85 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 12:52:51 +0200 Subject: [PATCH 04/11] remove temporaryID, use meetingID instead --- backend/src/graphql/models/#TableModel.ts# | 124 ++++++++++++++++++ .../graphql/resolvers/TableResolver.spec.ts | 8 +- .../src/graphql/resolvers/TableResolver.ts | 34 +---- .../graphql/resolvers/dal/handleOpenTables.ts | 1 - 4 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 backend/src/graphql/models/#TableModel.ts# diff --git a/backend/src/graphql/models/#TableModel.ts# b/backend/src/graphql/models/#TableModel.ts# new file mode 100644 index 000000000..7efc022ea --- /dev/null +++ b/backend/src/graphql/models/#TableModel.ts# @@ -0,0 +1,124 @@ +import { Meeting } from '@prisma/client' +import { ObjectType, Field, Int } from 'type-graphql' + +import { MeetingInfo, AttendeeInfo } from '#src/api/BBB' +import { UsersWithMeetings } from '#src/prisma' + +import { Attendee } from './AttendeeModel' +import { UserInMeeting } from './UserInMeetingModel' + +export const getAttendees = (meeting: MeetingInfo): Attendee[] => { + const attendees = + typeof meeting.attendees !== 'string' + ? Array.isArray(meeting.attendees.attendee) + ? meeting.attendees.attendee.map((a: AttendeeInfo) => new Attendee(a)) + : [meeting.attendees.attendee] + : [] + return attendees +} + +@ObjectType() +export class Table { + constructor(data: Pick) { + Object.assign(this, data) + } + + static fromMeeting(meeting: Meeting, usersWithMeetings: UsersWithMeetings[]) { + const { id, name } = meeting + const users = usersWithMeetings.map((u) => new UserInMeeting(u)) + return new Table({ id, name, public: meeting.public, users }) + } + + @Field(() => Int) + id: number + + @Field(() => String) + meetingID: string + + @Field() + name: string + + @Field() + public: boolean + + @Field(() => [UserInMeeting]) + users: UserInMeeting[] +} + +@ObjectType() +export class OpenTable { + constructor( + data: Pick< + OpenTable, + | 'id' + | 'meetingID' + | 'meetingName' + | 'participantCount' + | 'startTime' + | 'attendees' + | 'isModerator' + >, + ) { + Object.assign(this, data) + } + + static fromMeetingInfo(meeting: MeetingInfo, id: number, isModerator: boolean): OpenTable { + const { meetingID, meetingName, participantCount } = meeting + const startTime = meeting.startTime.toString() + const attendees = getAttendees(meeting) + return new OpenTable({ + id: String(id), + meetingID, + meetingName, + participantCount, + startTime, + attendees, + isModerator, + }) + } + + @Field() + id: string + + @Field() + meetingID: string + + @Field() + meetingName: string + + @Field(() => String) + startTime: string + + @Field(() => Int) + participantCount: number + + @Field(() => [Attendee]) + attendees: Attendee[] + + @Field() + isModerator: boolean +} + +@ObjectType() +export class OpenTables { + @Field(() => [OpenTable]) + permanentTables: OpenTable[] + + @Field(() => [OpenTable]) + mallTalkTables: OpenTable[] + + @Field(() => [OpenTable]) + projectTables: OpenTable[] +} + +@ObjectType() +export class JoinTable { + @Field() + link: string + + @Field() + type: string + + @Field() + isModerator: boolean +} diff --git a/backend/src/graphql/resolvers/TableResolver.spec.ts b/backend/src/graphql/resolvers/TableResolver.spec.ts index 045fe1ee3..c4acf6db4 100644 --- a/backend/src/graphql/resolvers/TableResolver.spec.ts +++ b/backend/src/graphql/resolvers/TableResolver.spec.ts @@ -374,7 +374,6 @@ describe('TableResolver', () => { data: { name: 'Pony Ville', meetingID: 'Pony Ville', - temporaryID: 'temp-id', }, }) tableId = meeting.id @@ -391,7 +390,7 @@ describe('TableResolver', () => { query, variables: { userName: 'Pinky Pie', - tableId: 'temp-id', + tableId: 'Pony Ville', }, }, { contextValue: mockContextValue() }, @@ -463,8 +462,7 @@ describe('TableResolver', () => { await prisma.meeting.create({ data: { name: 'Club of Rome', - meetingID: 'Club of Rome', - temporaryID: 'club-of-rome', + meetingID: 'club-of-rome', }, }) }) @@ -1223,8 +1221,6 @@ describe('TableResolver', () => { name: 'My Table', // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment meetingID: expect.any(String), - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - temporaryID: expect.any(String), attendeePW: 'w3VUvMcp', moderatorPW: 'MyPp9Zfq', voiceBridge: 255, diff --git a/backend/src/graphql/resolvers/TableResolver.ts b/backend/src/graphql/resolvers/TableResolver.ts index 33f7dfca8..b636fffa7 100644 --- a/backend/src/graphql/resolvers/TableResolver.ts +++ b/backend/src/graphql/resolvers/TableResolver.ts @@ -189,7 +189,7 @@ export class TableResolver { if (!dbMeeting) throw new Error('Meeting not found!') - const inviteLink = await createInviteLink(prisma)(dbMeeting.id) + const inviteLink = createInviteLink(dbMeeting.meetingID) await createBBBMeeting(prisma)({ meetingID: dbMeeting.meetingID, @@ -307,7 +307,7 @@ export class TableResolver { (table.user && table.user.id === user.id) || table.users.some((e) => e.userId === user.id && e.role === 'MODERATOR') ) { - const inviteLink = await createInviteLink(prisma)(table.id) + const inviteLink = createInviteLink(table.meetingID) const meeting = await createBBBMeeting(prisma)({ meetingID: table.meetingID, name: table.name, @@ -353,7 +353,7 @@ export class TableResolver { } = context const meeting = await prisma.meeting.findUnique({ where: { - temporaryID: tableId, + meetingID: tableId, }, include: { user: true, @@ -378,7 +378,7 @@ export class TableResolver { } = context const meeting = await prisma.meeting.findUnique({ where: { - temporaryID: tableId, + meetingID: tableId, }, }) if (!meeting) throw new Error('Table does not exist') @@ -657,29 +657,9 @@ const createMeetingID = (prisma: PrismaClient) => async (): Promise => { return meetingID } -const createInviteLink = - (prisma: PrismaClient) => - async (tableId: number): Promise => { - let temporaryID: string = uuidv4() - while ( - await prisma.meeting.count({ - where: { - temporaryID, - }, - }) - ) { - temporaryID = uuidv4() - } - await prisma.meeting.update({ - where: { - id: tableId, - }, - data: { - temporaryID, - }, - }) - return new URL(`join-table/${temporaryID}`, CONFIG.FRONTEND_URL).toString() - } +const createInviteLink = (tableId: string): string => { + return new URL(`join-table/${tableId}`, CONFIG.FRONTEND_URL).toString() +} const createBBBMeeting = (prisma: PrismaClient) => diff --git a/backend/src/graphql/resolvers/dal/handleOpenTables.ts b/backend/src/graphql/resolvers/dal/handleOpenTables.ts index b8e19e4ee..e19e5de97 100644 --- a/backend/src/graphql/resolvers/dal/handleOpenTables.ts +++ b/backend/src/graphql/resolvers/dal/handleOpenTables.ts @@ -15,7 +15,6 @@ export const handleOpenTables = async (): Promise => { }, }, data: { - temporaryID: null, attendeePW: null, moderatorPW: null, voiceBridge: null, From ae479c56c88950fe788c775446a03f8c964b40d4 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 12:54:00 +0200 Subject: [PATCH 05/11] expose meetingID in Table model --- backend/src/graphql/models/#TableModel.ts# | 124 --------------------- backend/src/graphql/models/TableModel.ts | 5 +- 2 files changed, 4 insertions(+), 125 deletions(-) delete mode 100644 backend/src/graphql/models/#TableModel.ts# diff --git a/backend/src/graphql/models/#TableModel.ts# b/backend/src/graphql/models/#TableModel.ts# deleted file mode 100644 index 7efc022ea..000000000 --- a/backend/src/graphql/models/#TableModel.ts# +++ /dev/null @@ -1,124 +0,0 @@ -import { Meeting } from '@prisma/client' -import { ObjectType, Field, Int } from 'type-graphql' - -import { MeetingInfo, AttendeeInfo } from '#src/api/BBB' -import { UsersWithMeetings } from '#src/prisma' - -import { Attendee } from './AttendeeModel' -import { UserInMeeting } from './UserInMeetingModel' - -export const getAttendees = (meeting: MeetingInfo): Attendee[] => { - const attendees = - typeof meeting.attendees !== 'string' - ? Array.isArray(meeting.attendees.attendee) - ? meeting.attendees.attendee.map((a: AttendeeInfo) => new Attendee(a)) - : [meeting.attendees.attendee] - : [] - return attendees -} - -@ObjectType() -export class Table { - constructor(data: Pick) { - Object.assign(this, data) - } - - static fromMeeting(meeting: Meeting, usersWithMeetings: UsersWithMeetings[]) { - const { id, name } = meeting - const users = usersWithMeetings.map((u) => new UserInMeeting(u)) - return new Table({ id, name, public: meeting.public, users }) - } - - @Field(() => Int) - id: number - - @Field(() => String) - meetingID: string - - @Field() - name: string - - @Field() - public: boolean - - @Field(() => [UserInMeeting]) - users: UserInMeeting[] -} - -@ObjectType() -export class OpenTable { - constructor( - data: Pick< - OpenTable, - | 'id' - | 'meetingID' - | 'meetingName' - | 'participantCount' - | 'startTime' - | 'attendees' - | 'isModerator' - >, - ) { - Object.assign(this, data) - } - - static fromMeetingInfo(meeting: MeetingInfo, id: number, isModerator: boolean): OpenTable { - const { meetingID, meetingName, participantCount } = meeting - const startTime = meeting.startTime.toString() - const attendees = getAttendees(meeting) - return new OpenTable({ - id: String(id), - meetingID, - meetingName, - participantCount, - startTime, - attendees, - isModerator, - }) - } - - @Field() - id: string - - @Field() - meetingID: string - - @Field() - meetingName: string - - @Field(() => String) - startTime: string - - @Field(() => Int) - participantCount: number - - @Field(() => [Attendee]) - attendees: Attendee[] - - @Field() - isModerator: boolean -} - -@ObjectType() -export class OpenTables { - @Field(() => [OpenTable]) - permanentTables: OpenTable[] - - @Field(() => [OpenTable]) - mallTalkTables: OpenTable[] - - @Field(() => [OpenTable]) - projectTables: OpenTable[] -} - -@ObjectType() -export class JoinTable { - @Field() - link: string - - @Field() - type: string - - @Field() - isModerator: boolean -} diff --git a/backend/src/graphql/models/TableModel.ts b/backend/src/graphql/models/TableModel.ts index f2016ff9d..7efc022ea 100644 --- a/backend/src/graphql/models/TableModel.ts +++ b/backend/src/graphql/models/TableModel.ts @@ -19,7 +19,7 @@ export const getAttendees = (meeting: MeetingInfo): Attendee[] => { @ObjectType() export class Table { - constructor(data: Pick) { + constructor(data: Pick) { Object.assign(this, data) } @@ -32,6 +32,9 @@ export class Table { @Field(() => Int) id: number + @Field(() => String) + meetingID: string + @Field() name: string From 1babb02e87576f73e076ce0ff7edc89b5d02695f Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 12:59:46 +0200 Subject: [PATCH 06/11] fix table model --- backend/src/graphql/models/TableModel.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/graphql/models/TableModel.ts b/backend/src/graphql/models/TableModel.ts index 7efc022ea..38ac74dfa 100644 --- a/backend/src/graphql/models/TableModel.ts +++ b/backend/src/graphql/models/TableModel.ts @@ -24,9 +24,9 @@ export class Table { } static fromMeeting(meeting: Meeting, usersWithMeetings: UsersWithMeetings[]) { - const { id, name } = meeting + const { id, meetingID, name } = meeting const users = usersWithMeetings.map((u) => new UserInMeeting(u)) - return new Table({ id, name, public: meeting.public, users }) + return new Table({ id, meetingID, name, public: meeting.public, users }) } @Field(() => Int) @@ -34,7 +34,7 @@ export class Table { @Field(() => String) meetingID: string - + @Field() name: string From bc88104b212a5ee8e25a08de85240dbe6797abf0 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 13:13:42 +0200 Subject: [PATCH 07/11] update meetingID everytime a meeting has ended on BBB --- backend/package.json | 1 + backend/src/graphql/resolvers/TableResolver.ts | 2 +- .../src/graphql/resolvers/dal/handleOpenTables.spec.ts | 9 ++++++--- backend/src/graphql/resolvers/dal/handleOpenTables.ts | 2 ++ backend/tsconfig.json | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index 3a7c672a9..5dd9ee360 100644 --- a/backend/package.json +++ b/backend/package.json @@ -103,6 +103,7 @@ "#graphql/*": "./src/graphql/*", "#inputs/*": "./src/graphql/inputs/*", "#models/*": "./src/graphql/models/*", + "#resolvers/*": "./src/graphql/resolvers/*", "#src/*": "./src/*", "#test/*": "./test/*", "#types/*": "./src/graphql/types/*" diff --git a/backend/src/graphql/resolvers/TableResolver.ts b/backend/src/graphql/resolvers/TableResolver.ts index b636fffa7..01c5fa9e1 100644 --- a/backend/src/graphql/resolvers/TableResolver.ts +++ b/backend/src/graphql/resolvers/TableResolver.ts @@ -643,7 +643,7 @@ const findUsersInMeetings = })) as UsersWithMeetings[] } -const createMeetingID = (prisma: PrismaClient) => async (): Promise => { +export const createMeetingID = (prisma: PrismaClient) => async (): Promise => { let meetingID: string = uuidv4() while ( await prisma.meeting.count({ diff --git a/backend/src/graphql/resolvers/dal/handleOpenTables.spec.ts b/backend/src/graphql/resolvers/dal/handleOpenTables.spec.ts index 659fff205..a56b726d6 100644 --- a/backend/src/graphql/resolvers/dal/handleOpenTables.spec.ts +++ b/backend/src/graphql/resolvers/dal/handleOpenTables.spec.ts @@ -153,7 +153,8 @@ describe('handleOpenTables', () => { }), expect.objectContaining({ name: 'Meeting 2', - meetingID: 'Meeting-2', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + meetingID: expect.any(String), createTime: null, }), ]), @@ -175,12 +176,14 @@ describe('handleOpenTables', () => { expect.arrayContaining([ expect.objectContaining({ name: 'Meeting 1', - meetingID: 'Meeting-1', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + meetingID: expect.any(String), createTime: null, }), expect.objectContaining({ name: 'Meeting 2', - meetingID: 'Meeting-2', + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + meetingID: expect.any(String), createTime: null, }), ]), diff --git a/backend/src/graphql/resolvers/dal/handleOpenTables.ts b/backend/src/graphql/resolvers/dal/handleOpenTables.ts index e19e5de97..3b9fad339 100644 --- a/backend/src/graphql/resolvers/dal/handleOpenTables.ts +++ b/backend/src/graphql/resolvers/dal/handleOpenTables.ts @@ -1,4 +1,5 @@ import { getMeetings, MeetingInfo } from '#api/BBB' +import { createMeetingID } from '#resolvers/TableResolver' import { pubSub } from '#src/graphql/pubSub' import { prisma } from '#src/prisma' @@ -15,6 +16,7 @@ export const handleOpenTables = async (): Promise => { }, }, data: { + meetingID: await createMeetingID(prisma)(), attendeePW: null, moderatorPW: null, voiceBridge: null, diff --git a/backend/tsconfig.json b/backend/tsconfig.json index a468f2eb6..cf128c858 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -13,6 +13,7 @@ "#graphql/*": ["./src/graphql/*"], "#inputs/*": ["./src/graphql/inputs/*"], "#models/*": ["./src/graphql/models/*"], + "#resolvers/*": ["./src/graphql/resolvers/*"], "#src/*": ["./src/*"], "#test/*": ["./test/*"], "#types/*": ["./src/graphql/types/*"] From af6fe55392e623f6297f5ff836658c5b8cea72e8 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 14:54:54 +0200 Subject: [PATCH 08/11] change param for join-table page from id to meetingID --- .../cockpit/my-tables/create-table/CreateTable.vue | 3 +++ .../cockpit/my-tables/create-table/TableCreated.vue | 4 +++- .../src/components/malltalk/interfaces/MyTableSettings.ts | 1 + frontend/src/components/malltalk/settings/TableSettings.vue | 2 ++ .../src/components/malltalk/settings/TableSettingsRoot.vue | 5 ++++- frontend/src/components/malltalk/setup/SubmitTable.vue | 6 +++++- frontend/src/components/malltalk/setup/TableSetup.vue | 4 ++++ frontend/src/graphql/mutations/createMyTableMutation.ts | 1 + frontend/src/graphql/mutations/createTableMutation.ts | 1 + frontend/src/graphql/mutations/updateMyTableMutation.ts | 1 + frontend/src/graphql/mutations/updateTableMutation.ts | 1 + frontend/src/graphql/queries/getTableName.ts | 2 +- frontend/src/graphql/queries/joinTableAsGuestQuery.ts | 2 +- frontend/src/pages/join-table/+Page.vue | 2 +- frontend/src/pages/join-table/useGetTableName.ts | 2 +- frontend/src/stores/tablesStore.ts | 5 +++-- frontend/src/stores/userStore.spec.ts | 3 +++ frontend/src/stores/userStore.ts | 1 + 18 files changed, 37 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/cockpit/my-tables/create-table/CreateTable.vue b/frontend/src/components/cockpit/my-tables/create-table/CreateTable.vue index fc3ecd731..fee8bb2e4 100644 --- a/frontend/src/components/cockpit/my-tables/create-table/CreateTable.vue +++ b/frontend/src/components/cockpit/my-tables/create-table/CreateTable.vue @@ -27,9 +27,11 @@ export type CreateTableModel = { name: string userIds: number[] tableId?: number + meetingID?: string } const createTableModel = reactive({ + meetingID: '', isPrivate: false, name: '', userIds: [], @@ -76,6 +78,7 @@ const onSubmit = async () => { } createTableModel.tableId = table.id + createTableModel.meetingID = table.meetingID stepControl.value?.next() } diff --git a/frontend/src/components/cockpit/my-tables/create-table/TableCreated.vue b/frontend/src/components/cockpit/my-tables/create-table/TableCreated.vue index 04e252f60..25718cbec 100644 --- a/frontend/src/components/cockpit/my-tables/create-table/TableCreated.vue +++ b/frontend/src/components/cockpit/my-tables/create-table/TableCreated.vue @@ -27,11 +27,13 @@ const pageContext = usePageContext() const { META } = pageContext.publicEnv const createTableModel = defineModel({ required: true }) +const meetingID = createTableModel.value.meetingID const tableId = createTableModel.value.tableId +if (!meetingID) throw new Error('Meeting ID is required') if (!tableId) throw new Error('Table ID is required') -const tableUrl = tablesStore.getJoinTableUrl(tableId, META.BASE_URL) +const tableUrl = tablesStore.getJoinTableUrl(meetingID, META.BASE_URL) const props = defineProps() const emit = defineEmits() diff --git a/frontend/src/components/malltalk/interfaces/MyTableSettings.ts b/frontend/src/components/malltalk/interfaces/MyTableSettings.ts index e445d2c5c..7b7f55f04 100644 --- a/frontend/src/components/malltalk/interfaces/MyTableSettings.ts +++ b/frontend/src/components/malltalk/interfaces/MyTableSettings.ts @@ -3,4 +3,5 @@ export default interface MyTableSettings { isPrivate: boolean users: number[] tableId?: number + meetingID?: string } diff --git a/frontend/src/components/malltalk/settings/TableSettings.vue b/frontend/src/components/malltalk/settings/TableSettings.vue index 081aa92fa..53c557bd6 100644 --- a/frontend/src/components/malltalk/settings/TableSettings.vue +++ b/frontend/src/components/malltalk/settings/TableSettings.vue @@ -30,12 +30,14 @@ const tableSettings = reactive({ name: myTable.value?.name || '', isPrivate: !myTable.value?.public || false, users: myTable.value?.users.map((u) => u.id) || [], + meetingID: myTable.value?.meetingID || '', }) watch(myTable, (value) => { tableSettings.name = value?.name || '' tableSettings.isPrivate = !value?.public || false tableSettings.users = value?.users.map((u) => u.id) || [] + tableSettings.meetingID = value?.meetingID || '' }) const steps: Step[] = [ diff --git a/frontend/src/components/malltalk/settings/TableSettingsRoot.vue b/frontend/src/components/malltalk/settings/TableSettingsRoot.vue index a635c229d..0a56579c9 100644 --- a/frontend/src/components/malltalk/settings/TableSettingsRoot.vue +++ b/frontend/src/components/malltalk/settings/TableSettingsRoot.vue @@ -37,9 +37,12 @@ import { usePageContext } from '#context/usePageContext' import { copyToClipboard } from '#src/utils/copyToClipboard' import { useTablesStore } from '#stores/tablesStore' +import type MyTableSettings from '#components/malltalk/interfaces/MyTableSettings' import type { StepEmits, StepProps } from '#components/steps/StepComponentTypes' import type { toast as Toast } from 'vue3-toastify' +const model = defineModel() + const toast = inject('toast') const copy = copyToClipboard(toast) @@ -57,7 +60,7 @@ const pageContext = usePageContext() const { META } = pageContext.publicEnv const tableId = computed(() => { - return pageContext.routeParams?.id ? Number(pageContext.routeParams.id) : null + return model.value?.meetingID ? model.value.meetingID : '' }) const tablesStore = useTablesStore() diff --git a/frontend/src/components/malltalk/setup/SubmitTable.vue b/frontend/src/components/malltalk/setup/SubmitTable.vue index 4823b335f..744ef3c6b 100644 --- a/frontend/src/components/malltalk/setup/SubmitTable.vue +++ b/frontend/src/components/malltalk/setup/SubmitTable.vue @@ -37,8 +37,12 @@ const pageContext = usePageContext() const { META } = pageContext.publicEnv const tableSettings = defineModel({ required: true }) + const tableId = tableSettings.value.tableId ?? 0 -const tableUrl = tablesStore.getJoinTableUrl(tableId, META.BASE_URL) + +const meetingID = tableSettings.value.meetingID ?? '' + +const tableUrl = tablesStore.getJoinTableUrl(meetingID, META.BASE_URL) const navigateToTable = async () => { if (!tableId) return diff --git a/frontend/src/components/malltalk/setup/TableSetup.vue b/frontend/src/components/malltalk/setup/TableSetup.vue index 680666d25..2077e8edf 100644 --- a/frontend/src/components/malltalk/setup/TableSetup.vue +++ b/frontend/src/components/malltalk/setup/TableSetup.vue @@ -31,6 +31,7 @@ const { defaultMyTableName } = storeToRefs(tablesStore) const resetTableSettings = () => { tableSettings.tableId = 0 + tableSettings.meetingID = '' tableSettings.name = defaultMyTableName.value tableSettings.isPrivate = false tableSettings.users = [] @@ -41,6 +42,7 @@ const tableSettings = reactive({ isPrivate: false, users: [], tableId: 0, + meetingID: '', }) resetTableSettings() @@ -102,6 +104,8 @@ const onSubmit = async () => { throw new Error(t('table.error')) } + tableSettings.meetingID = table.meetingID + tableSettings.tableId = await tablesStore.joinMyTable() if (!tableSettings.tableId) { throw new Error(t('table.error')) diff --git a/frontend/src/graphql/mutations/createMyTableMutation.ts b/frontend/src/graphql/mutations/createMyTableMutation.ts index f3d1f53a7..6737c30c4 100644 --- a/frontend/src/graphql/mutations/createMyTableMutation.ts +++ b/frontend/src/graphql/mutations/createMyTableMutation.ts @@ -4,6 +4,7 @@ export const createMyTableMutation = gql` mutation ($name: String!, $isPublic: Boolean!, $userIds: [Int]) { createMyTable(name: $name, isPublic: $isPublic, userIds: $userIds) { id + meetingID name public users { diff --git a/frontend/src/graphql/mutations/createTableMutation.ts b/frontend/src/graphql/mutations/createTableMutation.ts index 8df0ac64c..f938fa7f6 100644 --- a/frontend/src/graphql/mutations/createTableMutation.ts +++ b/frontend/src/graphql/mutations/createTableMutation.ts @@ -4,6 +4,7 @@ export const createTableMutation = gql(`mutation CreateTable($isPublic: Boolean!, $name: String!, $userIds: [Int]) { createTable(isPublic: $isPublic, name: $name, userIds: $userIds) { id + meetingID name public users { diff --git a/frontend/src/graphql/mutations/updateMyTableMutation.ts b/frontend/src/graphql/mutations/updateMyTableMutation.ts index 13e7b17ed..3f0051d36 100644 --- a/frontend/src/graphql/mutations/updateMyTableMutation.ts +++ b/frontend/src/graphql/mutations/updateMyTableMutation.ts @@ -4,6 +4,7 @@ export const updateMyTableMutation = gql` mutation ($name: String!, $isPublic: Boolean!, $userIds: [Int]) { updateMyTable(name: $name, isPublic: $isPublic, userIds: $userIds) { id + meetingID name public users { diff --git a/frontend/src/graphql/mutations/updateTableMutation.ts b/frontend/src/graphql/mutations/updateTableMutation.ts index 018f7943e..6a3af9329 100644 --- a/frontend/src/graphql/mutations/updateTableMutation.ts +++ b/frontend/src/graphql/mutations/updateTableMutation.ts @@ -4,6 +4,7 @@ export const updateTableMutation = gql` mutation ($tableId: Int!, $name: String, $isPublic: Boolean, $userIds: [Int]) { updateTable(tableId: $tableId, name: $name, isPublic: $isPublic, userIds: $userIds) { id + meetingID name public users { diff --git a/frontend/src/graphql/queries/getTableName.ts b/frontend/src/graphql/queries/getTableName.ts index 292c04e4c..2218552c8 100644 --- a/frontend/src/graphql/queries/getTableName.ts +++ b/frontend/src/graphql/queries/getTableName.ts @@ -1,7 +1,7 @@ import { gql } from 'graphql-tag' export const getTableNameQuery = gql` - query getTableName($tableId: Int!) { + query getTableName($tableId: String!) { getTableName(tableId: $tableId) } ` diff --git a/frontend/src/graphql/queries/joinTableAsGuestQuery.ts b/frontend/src/graphql/queries/joinTableAsGuestQuery.ts index 1605ecfea..090387e56 100644 --- a/frontend/src/graphql/queries/joinTableAsGuestQuery.ts +++ b/frontend/src/graphql/queries/joinTableAsGuestQuery.ts @@ -1,7 +1,7 @@ import { gql } from 'graphql-tag' export const joinTableAsGuestQuery = gql` - query ($tableId: Int!, $userName: String!) { + query ($tableId: String!, $userName: String!) { joinTableAsGuest(tableId: $tableId, userName: $userName) } ` diff --git a/frontend/src/pages/join-table/+Page.vue b/frontend/src/pages/join-table/+Page.vue index 478c529a9..8a9f54dac 100644 --- a/frontend/src/pages/join-table/+Page.vue +++ b/frontend/src/pages/join-table/+Page.vue @@ -57,7 +57,7 @@ import { joinTableAsGuestQuery } from '#queries/joinTableAsGuestQuery' const { t } = useI18n() const pageContext = usePageContext() -const tableId = Number(pageContext.routeParams?.id) +const tableId = pageContext.routeParams?.id const { tableName, isError } = useGetTableName(tableId) const title = computed(() => tableName.value ?? t('joinTablePage.publicTable')) diff --git a/frontend/src/pages/join-table/useGetTableName.ts b/frontend/src/pages/join-table/useGetTableName.ts index 6e2dd4679..646046e26 100644 --- a/frontend/src/pages/join-table/useGetTableName.ts +++ b/frontend/src/pages/join-table/useGetTableName.ts @@ -7,7 +7,7 @@ type GetTableNameResult = { getTableName: string } -export default function useGetTableName(tableId: number) { +export default function useGetTableName(tableId: string) { const { result, loading, error } = useQuery( getTableNameQuery, { tableId }, diff --git a/frontend/src/stores/tablesStore.ts b/frontend/src/stores/tablesStore.ts index 6e78d472b..25bedf142 100644 --- a/frontend/src/stores/tablesStore.ts +++ b/frontend/src/stores/tablesStore.ts @@ -46,6 +46,7 @@ export type UserInTable = { export type ProjectTable = { id: number + meetingID: string name: string public: boolean users: UserInTable[] @@ -258,8 +259,8 @@ export const useTablesStore = defineStore( const existsMyTable = computed(() => myTable.value !== null) const defaultMyTableName = computed(() => currentUser.value?.name ?? '') const getTableUri = (id: number): string => `/table/${id}` - const getJoinTableUri = (id: number): string => `/join-table/${id}` - const getJoinTableUrl = (id: number, baseUrl: string): string => + const getJoinTableUri = (id: string): string => `/join-table/${id}` + const getJoinTableUrl = (id: string, baseUrl: string): string => id ? new URL(getJoinTableUri(id), baseUrl).href : '' return { diff --git a/frontend/src/stores/userStore.spec.ts b/frontend/src/stores/userStore.spec.ts index 41d1d09f2..aa458b8e3 100644 --- a/frontend/src/stores/userStore.spec.ts +++ b/frontend/src/stores/userStore.spec.ts @@ -105,6 +105,7 @@ describe('User Store', () => { social: [{ id: 1, type: 'instagram', link: 'https://instagram.com' }], table: { id: 1234, + meetingID: 'my-table', name: 'My Table', public: false, users: [ @@ -137,6 +138,7 @@ describe('User Store', () => { social: [{ id: 1, type: 'instagram', link: 'https://instagram.com' }], table: { id: 1234, + meetingID: 'my-table', name: 'My Table', public: false, users: [ @@ -160,6 +162,7 @@ describe('User Store', () => { it('updates my table', () => { expect(userStore.getMyTable).toEqual({ id: 1234, + meetingID: 'my-table', name: 'My Table', public: false, users: [ diff --git a/frontend/src/stores/userStore.ts b/frontend/src/stores/userStore.ts index 94e3df890..10e3f9130 100644 --- a/frontend/src/stores/userStore.ts +++ b/frontend/src/stores/userStore.ts @@ -36,6 +36,7 @@ export type UserInTable = { export type MyTable = { id: number + meetingID: string name: string public: boolean users: UserInTable[] From ca2b31d319db67b94aa03d1e2b2e9722c8090942 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Tue, 15 Oct 2024 15:18:49 +0200 Subject: [PATCH 09/11] update snapshot --- frontend/src/pages/table/@id/__snapshots__/Page.test.ts.snap | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/pages/table/@id/__snapshots__/Page.test.ts.snap b/frontend/src/pages/table/@id/__snapshots__/Page.test.ts.snap index 5abedefa2..10de836d1 100644 --- a/frontend/src/pages/table/@id/__snapshots__/Page.test.ts.snap +++ b/frontend/src/pages/table/@id/__snapshots__/Page.test.ts.snap @@ -1697,7 +1697,6 @@ exports[`Table Page > renders 1`] = `
Date: Tue, 15 Oct 2024 16:08:35 +0200 Subject: [PATCH 10/11] get the correct meetingID for the link in an ongoing meeting --- .../malltalk/settings/TableSettingsRoot.vue | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/malltalk/settings/TableSettingsRoot.vue b/frontend/src/components/malltalk/settings/TableSettingsRoot.vue index 0a56579c9..5c13a5af9 100644 --- a/frontend/src/components/malltalk/settings/TableSettingsRoot.vue +++ b/frontend/src/components/malltalk/settings/TableSettingsRoot.vue @@ -24,6 +24,7 @@