diff --git a/backend/.env.dist b/backend/.env.dist index a2d1282487..82c1c38fdc 100644 --- a/backend/.env.dist +++ b/backend/.env.dist @@ -7,5 +7,4 @@ BREVO_NEWSLETTER_TEMPLATE_OPTIN="3" BREVO_NEWSLETTER_LIST="3" DATABASE_URL="mysql://root:@localhost:3306/dreammall.earth" -BBB_SHARED_SECRET="" -BBB_URL="" +ROOM_LINK="http://my-room.earth" diff --git a/backend/jest.config.json b/backend/jest.config.json index 9a5b9b93cd..8d82ecffb8 100644 --- a/backend/jest.config.json +++ b/backend/jest.config.json @@ -13,10 +13,10 @@ ], "coverageThreshold": { "global": { - "statements": 96, - "branches": 89, - "functions": 95, - "lines": 96 + "statements": 98, + "branches": 97, + "functions": 96, + "lines": 98 } }, "modulePathIgnorePatterns": ["/build/"], diff --git a/backend/package-lock.json b/backend/package-lock.json index 5191b9dae0..bad6ff6b0f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -13,10 +13,8 @@ "@getbrevo/brevo": "^2.1.1", "@prisma/client": "^5.13.0", "@vuepress/theme-default": "^2.0.0-rc.26", - "axios": "^1.6.8", "class-validator": "^0.14.1", "dotenv": "^16.4.5", - "fast-xml-parser": "^4.3.6", "graphql": "^16.8.1", "graphql-scalars": "^1.23.0", "jsonwebtoken": "^9.0.2", @@ -25,15 +23,13 @@ "reflect-metadata": "^0.2.2", "tsconfig-paths": "^4.2.0", "tslog": "^4.9.2", - "type-graphql": "^2.0.0-rc.1", - "uuid": "^9.0.1" + "type-graphql": "^2.0.0-rc.1" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.12.10", - "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "@vuepress/bundler-vite": "^2.0.0-rc.9", @@ -3082,12 +3078,6 @@ "integrity": "sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==", "dev": true }, - "node_modules/@types/uuid": { - "version": "9.0.8", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", - "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", - "dev": true - }, "node_modules/@types/validator": { "version": "13.11.9", "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", @@ -4541,16 +4531,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -6967,27 +6947,6 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, - "node_modules/fast-xml-parser": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.6.tgz", - "integrity": "sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - }, - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -7095,25 +7054,6 @@ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -11373,11 +11313,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/ps-tree": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", @@ -13575,11 +13510,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/backend/package.json b/backend/package.json index 106e920385..bab405068b 100644 --- a/backend/package.json +++ b/backend/package.json @@ -39,10 +39,8 @@ "@getbrevo/brevo": "^2.1.1", "@prisma/client": "^5.13.0", "@vuepress/theme-default": "^2.0.0-rc.26", - "axios": "^1.6.8", "class-validator": "^0.14.1", "dotenv": "^16.4.5", - "fast-xml-parser": "^4.3.6", "graphql": "^16.8.1", "graphql-scalars": "^1.23.0", "jsonwebtoken": "^9.0.2", @@ -51,15 +49,13 @@ "reflect-metadata": "^0.2.2", "tsconfig-paths": "^4.2.0", "tslog": "^4.9.2", - "type-graphql": "^2.0.0-rc.1", - "uuid": "^9.0.1" + "type-graphql": "^2.0.0-rc.1" }, "devDependencies": { "@eslint-community/eslint-plugin-eslint-comments": "^4.3.0", "@types/jest": "^29.5.12", "@types/jsonwebtoken": "^9.0.6", "@types/node": "^20.12.10", - "@types/uuid": "^9.0.8", "@typescript-eslint/eslint-plugin": "^7.8.0", "@typescript-eslint/parser": "^7.8.0", "@vuepress/bundler-vite": "^2.0.0-rc.9", diff --git a/backend/prisma/migrations/20240508040257_add_meeting/migration.sql b/backend/prisma/migrations/20240508040257_add_meeting/migration.sql deleted file mode 100644 index 4b755343b4..0000000000 --- a/backend/prisma/migrations/20240508040257_add_meeting/migration.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - Warnings: - - - A unique constraint covering the columns `[meetingId]` on the table `User` will be added. If there are existing duplicate values, this will fail. - -*/ --- AlterTable -ALTER TABLE `User` ADD COLUMN `meetingId` INTEGER NULL; - --- CreateTable -CREATE TABLE `Meeting` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `meetingID` VARCHAR(36) NOT NULL, - `attendeePW` VARCHAR(64) NOT NULL, - `moderatorPW` VARCHAR(64) NOT NULL, - `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `voiceBridge` INTEGER NOT NULL, - `dialNumber` VARCHAR(64) NOT NULL, - `createTime` INTEGER NOT NULL, - `createDate` DATETIME(3) NOT NULL, - - UNIQUE INDEX `Meeting_meetingID_key`(`meetingID`), - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateIndex -CREATE UNIQUE INDEX `User_meetingId_key` ON `User`(`meetingId`); - --- AddForeignKey -ALTER TABLE `User` ADD CONSTRAINT `User_meetingId_fkey` FOREIGN KEY (`meetingId`) REFERENCES `Meeting`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/20240508040932_add_name_to_meeting/migration.sql b/backend/prisma/migrations/20240508040932_add_name_to_meeting/migration.sql deleted file mode 100644 index e1c5578e5e..0000000000 --- a/backend/prisma/migrations/20240508040932_add_name_to_meeting/migration.sql +++ /dev/null @@ -1,8 +0,0 @@ -/* - Warnings: - - - Added the required column `name` to the `Meeting` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE `Meeting` ADD COLUMN `name` VARCHAR(254) NOT NULL; diff --git a/backend/prisma/migrations/20240508093355_meeting_fields_optional/migration.sql b/backend/prisma/migrations/20240508093355_meeting_fields_optional/migration.sql deleted file mode 100644 index d9ec649117..0000000000 --- a/backend/prisma/migrations/20240508093355_meeting_fields_optional/migration.sql +++ /dev/null @@ -1,7 +0,0 @@ --- AlterTable -ALTER TABLE `Meeting` MODIFY `attendeePW` VARCHAR(64) NULL, - MODIFY `moderatorPW` VARCHAR(64) NULL, - MODIFY `voiceBridge` INTEGER NULL, - MODIFY `dialNumber` VARCHAR(64) NULL, - MODIFY `createTime` INTEGER NULL, - MODIFY `createDate` DATETIME(3) NULL; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 3c32494187..b36cbdcca4 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -53,20 +53,4 @@ model User { username String @db.VarChar(100) @unique name String @db.VarChar(254) createdAt DateTime @default(now()) - meeting Meeting? @relation(fields: [meetingId], references: [id]) - meetingId Int? @unique -} - -model Meeting { - id Int @id @default(autoincrement()) - name String @db.VarChar(254) - meetingID String @db.VarChar(36) @unique - attendeePW String? @db.VarChar(64) - moderatorPW String? @db.VarChar(64) - createdAt DateTime @default(now()) - voiceBridge Int? - dialNumber String? @db.VarChar(64) - createTime Int? - createDate DateTime? - user User? } diff --git a/backend/src/api/BBB.spec.ts b/backend/src/api/BBB.spec.ts deleted file mode 100644 index 8c78de8a69..0000000000 --- a/backend/src/api/BBB.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -// eslint-disable-next-line import/named -import { AxiosHeaders, InternalAxiosRequestConfig } from 'axios' - -import { CONFIG } from '#config/config' - -import { createChecksum, addChecksumParam, joinMeetingLink } from './BBB' - -// values taken form https://docs.bigbluebutton.org/development/api/#usage -CONFIG.BBB_SHARED_SECRET = '639259d4-9dd8-4b25-bf01-95f9567eaf4b' -CONFIG.BBB_URL = 'https://my.url/' - -describe('createChecksum', () => { - describe('callName without slash', () => { - it('returns sha1 hash', () => { - expect( - createChecksum( - 'create', - 'name=Test+Meeting&meetingID=abc123&attendeePW=111222&moderatorPW=333444', - ), - ).toBe('1fcbb0c4fc1f039f73aa6d697d2db9ba7f803f17') - }) - }) - - describe('callName with slash', () => { - it('returns sha1 hash', () => { - expect( - createChecksum( - '/create', - 'name=Test+Meeting&meetingID=abc123&attendeePW=111222&moderatorPW=333444', - ), - ).toBe('1fcbb0c4fc1f039f73aa6d697d2db9ba7f803f17') - }) - }) - - describe('without params', () => { - it('returns sha1 hash', () => { - expect(createChecksum('/create')).toBe('8a21c9b7e3b18541974c9e78c0d0bfa790c665eb') - }) - }) -}) - -describe('addChecksumParam', () => { - const config: InternalAxiosRequestConfig = { - baseURL: CONFIG.BBB_URL, - headers: new AxiosHeaders(), - } - - describe('url /create', () => { - config.url = '/create' - - describe('no params', () => { - it('adds checksum', () => { - expect(addChecksumParam(config)).toMatchObject({ - params: { - checksum: '8a21c9b7e3b18541974c9e78c0d0bfa790c665eb', - }, - }) - }) - }) - - describe('with params', () => { - it('adds checksum', () => { - expect( - addChecksumParam({ - ...config, - params: { - name: 'Test Meeting', - meetingID: 'abc123', - attendeePW: '111222', - moderatorPW: '333444', - }, - }), - ).toMatchObject({ - params: { - name: 'Test Meeting', - meetingID: 'abc123', - attendeePW: '111222', - moderatorPW: '333444', - checksum: '1fcbb0c4fc1f039f73aa6d697d2db9ba7f803f17', - }, - }) - }) - }) - }) -}) - -describe('joinMeetingLink', () => { - it('returns a link to join the meeting', () => { - expect( - joinMeetingLink({ - fullName: 'User', - meetingID: 'My Meeting', - password: 'password', - // role: 'MODERATOR', - // createTime: 'now', - // userID: '1234', - }), - ).toBe( - 'https://my.url/join?fullName=User&meetingID=My+Meeting&password=password&redirect=true&checksum=d7fdddda59b530a5acb56e77d5683c4324ceac4f', - ) - }) -}) diff --git a/backend/src/api/BBB.ts b/backend/src/api/BBB.ts deleted file mode 100644 index 06cf9c43e8..0000000000 --- a/backend/src/api/BBB.ts +++ /dev/null @@ -1,175 +0,0 @@ -import { createHash } from 'crypto' -import querystring, { ParsedUrlQueryInput } from 'node:querystring' - -import axios, { InternalAxiosRequestConfig } from 'axios' -import { XMLParser } from 'fast-xml-parser' - -import { CONFIG } from '#config/config' - -const parser = new XMLParser() - -const axiosInstance = axios.create({ - baseURL: CONFIG.BBB_URL, - timeout: 25000, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, -}) - -export const addChecksumParam = ( - config: InternalAxiosRequestConfig, -): InternalAxiosRequestConfig => { - if (!config.params) { - config.params = { - checksum: createChecksum(config.url || CONFIG.BBB_URL), - } - } else { - const checksumParams = querystring - .stringify(config.params as ParsedUrlQueryInput | undefined) - .replace(/%20/g, '+') - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - config.params = { - ...config.params, - checksum: createChecksum(config.url || CONFIG.BBB_URL, checksumParams), - } - } - return config -} - -axiosInstance.interceptors.request.use(addChecksumParam, null, { synchronous: true }) - -export const createChecksum = (callName: string, params: string = ''): string => { - const hash = createHash('sha1') - hash.update( - (callName[0] === '/' ? callName.substring(1) : callName) + params + CONFIG.BBB_SHARED_SECRET, - ) - return hash.digest('hex') -} - -interface CreateMeetingResponse { - returncode: string - meetingID: string - internalMeetingID: string - parentMeetingID: string - attendeePW: string - moderatorPW: string - createTime: number - voiceBridge: number - dialNumber: string - createDate: Date - hasUserJoined: boolean - duration: number - hasBeenForciblyEnded: boolean - messageKey: string - message: string -} - -interface MeetingInfo { - meetingName: string - meetingID: string - internalMeetingID: string - createTime: number - createDate: Date - voiceBridge: number - dialNumber: string - attendeePW: string - moderatorPW: string - running: boolean - duration: number - hasUserJoined: boolean - recording: boolean - hasBeenForciblyEnded: boolean - startTime: number - endTime: number - participantCount: number - listenerCount: number - voiceParticipantCount: number - videoCount: number - maxUsers: number - moderatorCount: number - attendees: string - metadata: string - isBreakout: string -} - -interface GetMeetingsResponse { - returncode: string - meetings: string | { meeting: MeetingInfo[] } -} - -export const getMeetings = async () => { - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await axiosInstance.get('/getMeetings') - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const parsed: { - response: GetMeetingsResponse - } = parser.parse(data as string) - return parsed.response - } catch (err) { - // eslint-disable-next-line no-console - console.log(err) - } -} - -interface CreateMeetingOptions { - name: string - meetingID: string - // welcome?: string -} - -export const createMeeting = async (options: CreateMeetingOptions) => { - const { name, meetingID /*, welcome */ } = options - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const { data } = await axiosInstance.post( - '/create', - {}, - { - params: { - name, - meetingID, - // welcome, - }, - }, - ) - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const parsed: { - response: CreateMeetingResponse - } = parser.parse(data as string) - return parsed.response - } catch (err) { - // eslint-disable-next-line no-console - console.log(err) - } -} - -interface JoinMeetinLinkOptions { - fullName: string - meetingID: string - // role: 'MODERATOR' | 'VIEWER' - password: string - // createTime: string - // userID: string -} - -export const joinMeetingLink = (options: JoinMeetinLinkOptions): string => { - const params = new URLSearchParams({ - ...options, - redirect: 'true', - }).toString() - const checksum = createChecksum('join', params) - return CONFIG.BBB_URL + 'join?' + params + '&checksum=' + checksum -} - -/* -export const listHooks = async () => { - try { - const result = await axiosInstance.get('/hooks/list') - console.log(result) - } catch (err) { - console.log(err) - } -} - -*/ diff --git a/backend/src/auth/authChecker.spec.ts b/backend/src/auth/authChecker.spec.ts index 5649c466ef..727bc61bb1 100644 --- a/backend/src/auth/authChecker.spec.ts +++ b/backend/src/auth/authChecker.spec.ts @@ -1,51 +1,27 @@ import { ApolloServer } from '@apollo/server' -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import axios from 'axios' import { prisma } from '#src/prisma' import { createTestServer } from '#src/server/server' -jest.mock('axios', () => { - return { - create: jest.fn().mockImplementation(() => { - return { - interceptors: { - request: { - use: jest.fn(), - }, - }, - post: jest.fn().mockImplementation(() => ({ - data: {}, - })), - get: jest.fn().mockImplementation(() => ({ - data: {}, - })), - } - }), - } -}) - let testServer: ApolloServer beforeAll(async () => { testServer = await createTestServer() }) -// uses joinMyRoom query +// uses getRoom query describe('authChecker', () => { describe('no token in context', () => { it('returns access denied error', async () => { await expect( testServer.executeOperation({ - query: 'query { joinMyRoom }', + query: 'query { getRoom }', }), ).resolves.toMatchObject({ body: { kind: 'single', singleResult: { - data: { - joinMyRoom: null, - }, + data: null, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment errors: expect.arrayContaining([ expect.objectContaining({ @@ -69,7 +45,7 @@ describe('authChecker', () => { it('creates user in database', async () => { await testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'query { getRoom }', }, { contextValue: { @@ -88,7 +64,6 @@ describe('authChecker', () => { createdAt: expect.any(Date), name: 'User', username: 'mockedUser', - meetingId: null, }, ]) }) @@ -117,7 +92,7 @@ describe('authChecker', () => { it('has the same user in database', async () => { await testServer.executeOperation( { - query: 'query { joinMyRoom }', + query: 'query { getRoom }', }, { contextValue: { diff --git a/backend/src/config/config.ts b/backend/src/config/config.ts index 9329f7914d..de56ad1935 100644 --- a/backend/src/config/config.ts +++ b/backend/src/config/config.ts @@ -29,14 +29,13 @@ const BREVO = { : undefined, } -const BBB = { - BBB_SHARED_SECRET: process.env.BBB_SHARED_SECRET ?? 'unknown', - BBB_URL: process.env.BBB_URL ?? 'https://my.url', +const ROOMS = { + ROOM_LINK: process.env.ROOM_LINK ?? 'http://my-room.earth', } export const CONFIG = { ...BREVO, - ...BBB, + ...ROOMS, } // Config Checks diff --git a/backend/src/graphql/resolvers/RoomResolver.spec.ts b/backend/src/graphql/resolvers/RoomResolver.spec.ts index df0f4fc641..1c1853e2f4 100644 --- a/backend/src/graphql/resolvers/RoomResolver.spec.ts +++ b/backend/src/graphql/resolvers/RoomResolver.spec.ts @@ -1,13 +1,9 @@ import { ApolloServer } from '@apollo/server' -import { createMeeting, joinMeetingLink } from '#api/BBB' -import { prisma } from '#src/prisma' +import { CONFIG } from '#config/config' import { createTestServer } from '#src/server/server' -jest.mock('#api/BBB') - -const createMeetingMock = createMeeting as jest.MockedFunction -const joinMeetingLinkMock = joinMeetingLink as jest.MockedFunction +CONFIG.ROOM_LINK = 'http://bbb.dreammall.earth' let testServer: ApolloServer @@ -17,41 +13,17 @@ beforeAll(async () => { describe('RoomResolver', () => { describe('unauthorized', () => { - describe('createMyRoom', () => { - it('throws access denied', async () => { - await expect( - testServer.executeOperation({ - query: 'mutation($name: String!) { createMyRoom(name: $name) { id } }', - variables: { name: 'My Room' }, - }), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { createMyRoom: null }, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - errors: expect.arrayContaining([ - expect.objectContaining({ - message: 'Access denied! You need to be authenticated to perform this action!', - }), - ]), - }, - }, - }) - }) - }) - - describe('joinMyRoom', () => { - it('throws access denied', async () => { + describe('getRoom Quey', () => { + it('returns the room link', async () => { await expect( testServer.executeOperation({ - query: 'query { joinMyRoom }', + query: 'query { getRoom }', }), ).resolves.toMatchObject({ body: { kind: 'single', singleResult: { - data: { joinMyRoom: null }, + data: null, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment errors: expect.arrayContaining([ expect.objectContaining({ @@ -66,185 +38,28 @@ describe('RoomResolver', () => { }) describe('authorized', () => { - describe('createMyRoom', () => { - describe.skip('no user in context', () => { - it('returns null', async () => { - await expect( - testServer.executeOperation( - { - query: 'mutation($name: String!) { createMyRoom(name: $name) { id } }', - variables: { name: 'My Room' }, - }, - { - contextValue: { - token: 'token', - user: undefined, - }, - }, - ), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { createMyRoom: null }, - errors: undefined, - }, - }, - }) - }) - }) - - describe('meeting does not exist', () => { - it('returns Room', async () => { - await expect( - testServer.executeOperation( - { - query: 'mutation($name: String!) { createMyRoom(name: $name) { id name } }', - variables: { name: 'My Room' }, - }, - { - contextValue: { - token: 'token', - user: undefined, - }, - }, - ), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { - createMyRoom: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - id: expect.any(Number), - name: 'My Room', - }, - }, - errors: undefined, - }, - }, - }) - }) - - it('creates meeting in database', async () => { - await expect( - prisma.user.findFirst({ - include: { - meeting: true, - }, - }), - ).resolves.toMatchObject({ - meeting: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - id: expect.any(Number), - name: 'My Room', - }, - }) - }) - }) - - describe('meeting exists', () => { - it('returns existing Room', async () => { - await expect( - testServer.executeOperation( - { - query: 'mutation($name: String!) { createMyRoom(name: $name) { id name } }', - variables: { name: 'New Room' }, - }, - { - contextValue: { - token: 'token', - user: undefined, - }, - }, - ), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { - createMyRoom: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - id: expect.any(Number), - name: 'My Room', - }, - }, - errors: undefined, - }, + describe('getRoom Quey', () => { + it('returns the room link', async () => { + await expect( + testServer.executeOperation( + { + query: 'query { getRoom }', }, - }) - }) - }) - }) - - describe('joinMyRoom', () => { - describe('createMeeting returns undefined', () => { - it('returns null', async () => { - createMeetingMock.mockResolvedValue(undefined) - await expect( - testServer.executeOperation( - { - query: 'query { joinMyRoom }', - }, - { - contextValue: { - token: 'token', - }, - }, - ), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { joinMyRoom: null }, - errors: undefined, + { + contextValue: { + token: 'token', }, }, - }) - }) - }) - - describe('createMeeting returns meeting', () => { - it('returns link to the meeting', async () => { - joinMeetingLinkMock.mockReturnValue('https://my-link') - createMeetingMock.mockResolvedValue({ - returncode: 'SUCCESS', - meetingID: 'xxx', - internalMeetingID: 'b60d121b438a380c343d5ec3c2037564b82ffef3-1715231322715', - parentMeetingID: 'bbb-none', - attendeePW: 'w3VUvMcp', - moderatorPW: 'MyPp9Zfq', - createTime: 1715231322715, - voiceBridge: 255, - dialNumber: '613-555-1234', - createDate: new Date(), - hasUserJoined: false, - duration: 0, - hasBeenForciblyEnded: false, - messageKey: '', - message: '', - }) - - await expect( - testServer.executeOperation( - { - query: 'query { joinMyRoom }', - }, - { - contextValue: { - token: 'token', - }, - }, - ), - ).resolves.toMatchObject({ - body: { - kind: 'single', - singleResult: { - data: { joinMyRoom: 'https://my-link' }, - errors: undefined, + ), + ).resolves.toMatchObject({ + body: { + kind: 'single', + singleResult: { + data: { + getRoom: 'http://bbb.dreammall.earth', }, }, - }) + }, }) }) }) diff --git a/backend/src/graphql/resolvers/RoomResolver.ts b/backend/src/graphql/resolvers/RoomResolver.ts index 2a7d8cb10b..c6dc521cd2 100644 --- a/backend/src/graphql/resolvers/RoomResolver.ts +++ b/backend/src/graphql/resolvers/RoomResolver.ts @@ -1,115 +1,13 @@ -import { Meeting } from '@prisma/client' -import { - ObjectType, - Field, - Int, - Resolver, - Mutation, - Query, - Authorized, - Ctx, - Arg, -} from 'type-graphql' -// eslint-disable-next-line import/named -import { v4 as uuidv4 } from 'uuid' +import { Resolver, Query, Authorized } from 'type-graphql' -// import { createMeeting, getMeetings } from '#api/BBB' -import { createMeeting, joinMeetingLink } from '#api/BBB' -import { prisma } from '#src/prisma' -import { Context } from '#src/server/context' - -@ObjectType() -class Room { - constructor(meeting: Meeting) { - this.id = meeting.id - this.name = meeting.name - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - @Field((type) => Int) - id: number - - @Field() - name: string -} +import { CONFIG } from '#config/config' @Resolver() export class RoomResolver { @Authorized() - @Mutation(() => Room, { nullable: true }) - async createMyRoom(@Arg('name') name: string, @Ctx() context: Context): Promise { - const { user } = context - if (!user) return null - if (user.meetingId) { - const meeting = await prisma.meeting.findUnique({ - where: { - id: user.meetingId, - }, - }) - if (meeting) return new Room(meeting) - else return null - } - - let meetingID: string = uuidv4() - while ( - await prisma.meeting.count({ - where: { - meetingID, - }, - }) - ) { - meetingID = uuidv4() - } - - const meeting = await prisma.meeting.create({ - data: { - name, - meetingID, - }, - }) - - await prisma.user.update({ - where: { id: user.id }, - data: { meetingId: meeting.id }, - }) - return meeting - } - - @Authorized() - @Query(() => String, { nullable: true }) - async joinMyRoom(@Ctx() context: Context): Promise { - const { user } = context - if (!user) return null - const meeting = await createMeeting({ - name: 'Dreammall Entwicklung', - meetingID: 'Dreammall-Entwicklung', - }) - if (!meeting) return null - return joinMeetingLink({ - fullName: user.name, - meetingID: 'Dreammall-Entwicklung', - password: meeting.moderatorPW, - // role: 'MODERATOR', - // createTime: meeting.createTime.toString(), - // userID: user.id.toString(), - }) - } - - /* - @Query(() => Boolean) - async test(): Promise { - try { - const result = await createMeeting({ - name: 'My Meeting ßß', - meetingID: 'xxx', - }) - console.log(result) - // const test = await getMeetings() - } catch (err) { - console.log(err) - return false - } - return true + @Query(() => String) + // eslint-disable-next-line @typescript-eslint/require-await + async getRoom(): Promise { + return CONFIG.ROOM_LINK } - */ } diff --git a/frontend/src/graphql/queries/joinMyRoomQuery.ts b/frontend/src/graphql/queries/getRoomQuery.ts similarity index 50% rename from frontend/src/graphql/queries/joinMyRoomQuery.ts rename to frontend/src/graphql/queries/getRoomQuery.ts index 60bd4f158c..f507d075d3 100644 --- a/frontend/src/graphql/queries/joinMyRoomQuery.ts +++ b/frontend/src/graphql/queries/getRoomQuery.ts @@ -1,7 +1,7 @@ import { gql } from 'graphql-tag' -export const joinMyRoomQuery = gql` +export const getRoomQuery = gql` query { - joinMyRoom + getRoom } ` diff --git a/frontend/src/pages/index/+Page.vue b/frontend/src/pages/index/+Page.vue index 17ba1c70ac..2dd9ff761c 100644 --- a/frontend/src/pages/index/+Page.vue +++ b/frontend/src/pages/index/+Page.vue @@ -35,7 +35,7 @@ import { inject } from 'vue' import MainButton from '#components/buttons/MainButton.vue' import DefaultLayout from '#layouts/DefaultLayout.vue' -import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' +import { getRoomQuery } from '#queries/getRoomQuery' import { AUTH } from '#src/env.js' import { useAuthStore } from '#stores/authStore.js' @@ -45,11 +45,8 @@ const apolloClient = inject>(DefaultApolloClient) const enterRoom = async () => { try { - const result = await apolloClient?.query({ - query: joinMyRoomQuery, - fetchPolicy: 'network-only', - }) - window.open(result?.data?.joinMyRoom, '_blank') + const result = await apolloClient?.query({ query: getRoomQuery, fetchPolicy: 'network-only' }) + window.location.href = result?.data?.getRoom // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { // eslint-disable-next-line no-console diff --git a/frontend/src/pages/index/Page.test.ts b/frontend/src/pages/index/Page.test.ts index 6df5c892b4..5199bd0d4e 100644 --- a/frontend/src/pages/index/Page.test.ts +++ b/frontend/src/pages/index/Page.test.ts @@ -3,17 +3,17 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' import { Component, h } from 'vue' import { VApp } from 'vuetify/components' -import { joinMyRoomQuery } from '#queries/joinMyRoomQuery' +import { getRoomQuery } from '#queries/getRoomQuery' import { mockClient } from '#tests/mock.apolloClient' import IndexPage from './+Page.vue' import { title } from './+title' -const joinMyRoomQueryMock = vi.fn() +const getRoomQueryMock = vi.fn() mockClient.setRequestHandler( - joinMyRoomQuery, - joinMyRoomQueryMock.mockResolvedValue({ data: { joinMyRoom: 'http://some.url' } }), + getRoomQuery, + getRoomQueryMock.mockResolvedValue({ data: { getRoom: 'http://some.url' } }), ) describe('IndexPage', () => { @@ -42,7 +42,6 @@ describe('IndexPage', () => { describe('room button', () => { const consoleSpy = vi.spyOn(global.console, 'log') - const windowOpenSpy = vi.spyOn(window, 'open') describe('without error', () => { beforeEach(async () => { @@ -51,18 +50,18 @@ describe('IndexPage', () => { }) it('calls the API', () => { - expect(joinMyRoomQueryMock).toBeCalled() + expect(getRoomQueryMock).toBeCalled() }) - it.skip('opens url in new tab', () => { - expect(windowOpenSpy).toBeCalledWith('http://some.url/', '_blank') + it('redirects to url', () => { + expect(global.window.location.href).toBe('http://some.url/') }) }) describe('with error', () => { beforeEach(() => { vi.clearAllMocks() - joinMyRoomQueryMock.mockRejectedValue({ message: 'Aua!' }) + getRoomQueryMock.mockRejectedValue({ message: 'Aua!' }) }) it('logs error message', async () => {