From 7da9ec6ee41c847a06349ec2dbea5918d568cc4d Mon Sep 17 00:00:00 2001
From: Marcos Spessatto Defendi <marcos.defendi@rocket.chat>
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 e1574ce0455..18289cf6736 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<string, 0 | 1>;
-
 			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 c8f9fd86890..ff144e08cfd 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<string, string | number | null | undefined>);
 			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 5e8bd0c0fac..f5930435e02 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 11ba7622bfe..53d167129dd 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<string> {
+	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 1568001f3aa..930271fe846 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 0380d954348..340561d8a15 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 750ca5f0eee..50ded4fc125 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<CustomSoundsList>(CustomSoundsListSchema);
-
 type ConnectorExtensionGetRegistrationInfoByUserId = { id: string };
 
 const ConnectorExtensionGetRegistrationInfoByUserIdSchema: JSONSchemaType<ConnectorExtensionGetRegistrationInfoByUserId> = {
-- 
GitLab