From 7da9ec6ee41c847a06349ec2dbea5918d568cc4d Mon Sep 17 00:00:00 2001 From: Marcos Spessatto Defendi Date: Wed, 16 Oct 2024 21:27:44 -0300 Subject: [PATCH] chore!: make custom sounds page stop to use query param (#33499) --- .../app/api/server/helpers/parseJsonQuery.ts | 2 +- .../meteor/app/api/server/v1/custom-sounds.ts | 16 ++- .../CustomSoundsTable/CustomSoundsTable.tsx | 3 +- .../tests/end-to-end/api/custom-sounds.ts | 130 +++++++++++------- packages/rest-typings/src/index.ts | 1 + packages/rest-typings/src/v1/customSounds.ts | 9 +- packages/rest-typings/src/v1/voip.ts | 29 ---- 7 files changed, 106 insertions(+), 84 deletions(-) diff --git a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts index e1574ce04559..18289cf67361 100644 --- a/apps/meteor/app/api/server/helpers/parseJsonQuery.ts +++ b/apps/meteor/app/api/server/helpers/parseJsonQuery.ts @@ -59,6 +59,7 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ '/api/v1/channels.files', '/api/v1/integrations.list', '/api/v1/custom-user-status.list', + '/api/v1/custom-sounds.list', ].includes(route); const isUnsafeQueryParamsAllowed = process.env.ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS?.toUpperCase() === 'TRUE'; @@ -70,7 +71,6 @@ export async function parseJsonQuery(api: PartialThis): Promise<{ try { apiDeprecationLogger.parameter(route, 'fields', '8.0.0', response, messageGenerator); fields = JSON.parse(params.fields) as Record; - Object.entries(fields).forEach(([key, value]) => { if (value !== 1 && value !== 0) { throw new Meteor.Error('error-invalid-sort-parameter', `Invalid fields parameter: ${key}`, { diff --git a/apps/meteor/app/api/server/v1/custom-sounds.ts b/apps/meteor/app/api/server/v1/custom-sounds.ts index c8f9fd86890d..ff144e08cfd4 100644 --- a/apps/meteor/app/api/server/v1/custom-sounds.ts +++ b/apps/meteor/app/api/server/v1/custom-sounds.ts @@ -1,16 +1,26 @@ import { CustomSounds } from '@rocket.chat/models'; +import { isCustomSoundsListProps } from '@rocket.chat/rest-typings'; +import { escapeRegExp } from '@rocket.chat/string-helpers'; import { API } from '../api'; import { getPaginationItems } from '../helpers/getPaginationItems'; API.v1.addRoute( 'custom-sounds.list', - { authRequired: true }, + { authRequired: true, validateParams: isCustomSoundsListProps }, { async get() { - const { offset, count } = await getPaginationItems(this.queryParams); + const { offset, count } = await getPaginationItems(this.queryParams as Record); const { sort, query } = await this.parseJsonQuery(); - const { cursor, totalCount } = CustomSounds.findPaginated(query, { + + const { name } = this.queryParams; + + const filter = { + ...query, + ...(name ? { name: { $regex: escapeRegExp(name as string), $options: 'i' } } : {}), + }; + + const { cursor, totalCount } = CustomSounds.findPaginated(filter, { sort: sort || { name: 1 }, skip: offset, limit: count, diff --git a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx index 5e8bd0c0fac9..f5930435e025 100644 --- a/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx +++ b/apps/meteor/client/views/admin/customSounds/CustomSoundsTable/CustomSoundsTable.tsx @@ -1,6 +1,5 @@ import { Pagination, States, StatesIcon, StatesActions, StatesAction, StatesTitle } from '@rocket.chat/fuselage'; import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { escapeRegExp } from '@rocket.chat/string-helpers'; import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; import type { MutableRefObject } from 'react'; @@ -34,7 +33,7 @@ const CustomSoundsTable = ({ reload, onClick }: CustomSoundsTableProps) => { const query = useDebouncedValue( useMemo( () => ({ - query: JSON.stringify({ name: { $regex: escapeRegExp(text), $options: 'i' } }), + name: text, sort: `{ "${sortBy}": ${sortDirection === 'asc' ? 1 : -1} }`, ...(itemsPerPage && { count: itemsPerPage }), ...(current && { offset: current }), diff --git a/apps/meteor/tests/end-to-end/api/custom-sounds.ts b/apps/meteor/tests/end-to-end/api/custom-sounds.ts index 11ba7622bfe0..53d167129dd2 100644 --- a/apps/meteor/tests/end-to-end/api/custom-sounds.ts +++ b/apps/meteor/tests/end-to-end/api/custom-sounds.ts @@ -7,9 +7,76 @@ import { before, describe, it, after } from 'mocha'; import { getCredentials, api, request, credentials } from '../../data/api-data'; +async function insertOrUpdateSound(fileName: string, fileId?: string): Promise { + fileId = fileId ?? ''; + + await request + .post(api('method.call/insertOrUpdateSound')) + .set(credentials) + .send({ + message: JSON.stringify({ + msg: 'method', + id: '1', + method: 'insertOrUpdateSound', + params: [{ name: fileName, extension: 'mp3', newFile: true }], + }), + }) + .expect(200) + .expect((res) => { + fileId = JSON.parse(res.body.message).result; + }); + + return fileId; +} + +async function uploadCustomSound(binary: string, fileName: string, fileId: string) { + await request + .post(api('method.call/uploadCustomSound')) + .set(credentials) + .send({ + message: JSON.stringify({ + msg: 'method', + id: '2', + method: 'uploadCustomSound', + params: [binary, 'audio/wav', { name: fileName, extension: 'wav', newFile: true, _id: fileId }], + }), + }) + .expect(200); +} + describe('[CustomSounds]', () => { + const fileName = `test-file-${randomUUID()}`; + let fileId: string; + let fileId2: string; + let uploadDate: unknown; + before((done) => getCredentials(done)); + before(async () => { + const data = readFileSync(path.resolve(__dirname, '../../mocks/files/audio_mock.wav')); + const binary = data.toString('binary'); + + fileId = await insertOrUpdateSound(fileName); + fileId2 = await insertOrUpdateSound(`${fileName}-2`); + + await uploadCustomSound(binary, fileName, fileId); + await uploadCustomSound(binary, `${fileName}-2`, fileId2); + }); + + after(() => + request + .post(api('method.call/deleteCustomSound')) + .set(credentials) + .send({ + message: JSON.stringify({ + msg: 'method', + id: '33', + method: 'deleteCustomSound', + params: [fileId], + }), + }), + ); + describe('[/custom-sounds.list]', () => { it('should return custom sounds', (done) => { void request @@ -41,59 +108,28 @@ describe('[CustomSounds]', () => { }) .end(done); }); - }); - - describe('Accessing custom sounds', () => { - let fileId: string; - const fileName = `test-file-${randomUUID()}`; - let uploadDate: unknown; - - before(async () => { - const data = readFileSync(path.resolve(__dirname, '../../mocks/files/audio_mock.wav')); - const binary = data.toString('binary'); - await request - .post(api('method.call/insertOrUpdateSound')) + it('should return custom sounds filtering it using the `name` parameter', (done) => { + void request + .get(api('custom-sounds.list')) .set(credentials) - .send({ - message: JSON.stringify({ - msg: 'method', - id: '1', - method: 'insertOrUpdateSound', - params: [{ name: fileName, extension: 'mp3', newFile: true }], - }), - }) .expect(200) + .query({ + name: `${fileName}-2`, + count: 5, + offset: 0, + }) .expect((res) => { - fileId = JSON.parse(res.body.message).result; - }); - await request - .post(api('method.call/uploadCustomSound')) - .set(credentials) - .send({ - message: JSON.stringify({ - msg: 'method', - id: '2', - method: 'uploadCustomSound', - params: [binary, 'audio/wav', { name: fileName, extension: 'wav', newFile: true, _id: fileId }], - }), + expect(res.body).to.have.property('sounds').and.to.be.an('array'); + expect(res.body).to.have.property('total').to.equal(1); + expect(res.body).to.have.property('offset').to.equal(0); + expect(res.body).to.have.property('count').to.equal(1); + expect(res.body.sounds[0]._id).to.be.equal(fileId2); }) - .expect(200); + .end(done); }); + }); - after(() => - request - .post(api('method.call/deleteCustomSound')) - .set(credentials) - .send({ - message: JSON.stringify({ - msg: 'method', - id: '33', - method: 'deleteCustomSound', - params: [fileId], - }), - }), - ); - + describe('Accessing custom sounds', () => { it('should return forbidden if the there is no fileId on the url', (done) => { void request .get('/custom-sounds/') diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 1568001f3aa8..930271fe846e 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -222,6 +222,7 @@ export * from './v1/videoConference'; export * from './v1/assets'; export * from './v1/channels'; export * from './v1/customUserStatus'; +export * from './v1/customSounds'; export * from './v1/subscriptionsEndpoints'; export * from './v1/mailer'; export * from './v1/mailer/MailerParamsPOST'; diff --git a/packages/rest-typings/src/v1/customSounds.ts b/packages/rest-typings/src/v1/customSounds.ts index 0380d954348f..340561d8a156 100644 --- a/packages/rest-typings/src/v1/customSounds.ts +++ b/packages/rest-typings/src/v1/customSounds.ts @@ -8,7 +8,7 @@ const ajv = new Ajv({ coerceTypes: true, }); -type CustomSoundsList = PaginatedRequest<{ query: string }>; +type CustomSoundsList = PaginatedRequest<{ name?: string }>; const CustomSoundsListSchema = { type: 'object', @@ -25,11 +25,16 @@ const CustomSoundsListSchema = { type: 'string', nullable: true, }, + name: { + type: 'string', + nullable: true, + }, query: { type: 'string', + nullable: true, }, }, - required: ['query'], + required: [], additionalProperties: false, }; diff --git a/packages/rest-typings/src/v1/voip.ts b/packages/rest-typings/src/v1/voip.ts index 750ca5f0eee8..50ded4fc1255 100644 --- a/packages/rest-typings/src/v1/voip.ts +++ b/packages/rest-typings/src/v1/voip.ts @@ -21,35 +21,6 @@ const ajv = new Ajv({ coerceTypes: true, }); -/** *************************************************/ -type CustomSoundsList = PaginatedRequest<{ query: string }>; - -const CustomSoundsListSchema = { - type: 'object', - properties: { - count: { - type: 'number', - nullable: true, - }, - offset: { - type: 'number', - nullable: true, - }, - sort: { - type: 'string', - nullable: true, - }, - query: { - type: 'string', - nullable: true, - }, - }, - required: [], - additionalProperties: false, -}; - -export const isCustomSoundsListProps = ajv.compile(CustomSoundsListSchema); - type ConnectorExtensionGetRegistrationInfoByUserId = { id: string }; const ConnectorExtensionGetRegistrationInfoByUserIdSchema: JSONSchemaType = {