Skip to content

Commit f1ed33c

Browse files
committed
refactor: Migrate livechat/users endpoints to OpenAPI with AJV validation
- Migrate livechat/users/:type and livechat/users/:type/:_id from addRoute() to chained .get()/.post()/.delete() pattern - Add local AJV schemas for query, body, and response validation - Add ExtractRoutesFromAPI type augmentation for auto-generated Endpoints types - Remove manual type definitions and schemas from rest-typings - Update all client consumers to use parameterized route paths
1 parent 16bfe5c commit f1ed33c

File tree

15 files changed

+28
-70
lines changed

15 files changed

+28
-70
lines changed

apps/meteor/app/livechat/imports/server/rest/users.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import { Users } from '@rocket.chat/models';
22
import { ajv, validateBadRequestErrorResponse, validateUnauthorizedErrorResponse, validateForbiddenErrorResponse } from '@rocket.chat/rest-typings';
33

44
import { API } from '../../../../api/server';
5+
import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass';
56
import { getPaginationItems } from '../../../../api/server/helpers/getPaginationItems';
67
import { hasAtLeastOnePermissionAsync } from '../../../../authorization/server/functions/hasPermission';
78
import { findAgents, findManagers } from '../../../server/api/lib/users';
89
import { addManager, addAgent, removeAgent, removeManager } from '../../../server/lib/omni-users';
910

10-
// --- Local types and AJV schemas (moved from rest-typings) ---
11-
1211
type LivechatUsersManagerGETLocal = {
1312
text?: string;
1413
fields?: string;
@@ -55,8 +54,6 @@ const POSTLivechatUsersTypeLocalSchema = {
5554

5655
const isPOSTLivechatUsersTypeLocal = ajv.compile<POSTLivechatUsersTypeLocal>(POSTLivechatUsersTypeLocalSchema);
5756

58-
// --- Response schemas ---
59-
6057
const paginatedUsersResponseSchema = ajv.compile<{
6158
users: object[];
6259
count: number;
@@ -65,8 +62,6 @@ const paginatedUsersResponseSchema = ajv.compile<{
6562
}>({
6663
type: 'object',
6764
properties: {
68-
// ILivechatAgent is not registered in typia (extends IUser with livechat-specific fields),
69-
// so we use { type: 'object' } as a fallback.
7065
users: { type: 'array', items: { type: 'object' } },
7166
count: { type: 'number' },
7267
offset: { type: 'number' },
@@ -108,7 +103,7 @@ const getUserByIdResponseSchema = ajv.compile<{ user: object | null }>({
108103

109104
const emptyStringArray: string[] = [];
110105

111-
API.v1
106+
const livechatUsersEndpoints = API.v1
112107
.get(
113108
'livechat/users/:type',
114109
{
@@ -254,3 +249,10 @@ API.v1
254249
},
255250
);
256251

252+
type LivechatUsersEndpoints = ExtractRoutesFromAPI<typeof livechatUsersEndpoints>;
253+
254+
declare module '@rocket.chat/rest-typings' {
255+
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface
256+
interface Endpoints extends LivechatUsersEndpoints {}
257+
}
258+

apps/meteor/client/views/omnichannel/agents/AgentEditWithData.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { omnichannelQueryKeys } from '../../../lib/queryKeys';
1212
const AgentEditWithData = ({ uid }: { uid: ILivechatAgent['_id'] }): ReactElement => {
1313
const { t } = useTranslation();
1414

15-
const getAgentById = useEndpoint('GET', '/v1/livechat/users/agent/:_id', { _id: uid });
15+
const getAgentById = useEndpoint('GET', '/v1/livechat/users/:type/:_id', { type: 'agent', _id: uid });
1616
const getAgentDepartments = useEndpoint('GET', '/v1/livechat/agents/:agentId/departments', { agentId: uid });
1717

1818
const { data, isPending, error } = useQuery({

apps/meteor/client/views/omnichannel/agents/AgentInfo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type AgentInfoProps = {
2626
const AgentInfo = ({ uid }: AgentInfoProps) => {
2727
const { t } = useTranslation();
2828
const router = useRouter();
29-
const getAgentById = useEndpoint('GET', '/v1/livechat/users/agent/:_id', { _id: uid });
29+
const getAgentById = useEndpoint('GET', '/v1/livechat/users/:type/:_id', { type: 'agent', _id: uid });
3030
const { data, isPending, isError } = useQuery({
3131
queryKey: ['livechat-getAgentInfoById', uid],
3232
queryFn: async () => getAgentById(),

apps/meteor/client/views/omnichannel/agents/AgentsTable/AddAgent.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ const AddAgent = () => {
1717

1818
const usernameFieldId = useId();
1919

20-
const { mutateAsync: saveAction } = useEndpointMutation('POST', '/v1/livechat/users/agent', {
20+
const { mutateAsync: saveAction } = useEndpointMutation('POST', '/v1/livechat/users/:type', {
21+
keys: { type: 'agent' },
2122
onSuccess: () => {
2223
queryClient.invalidateQueries({ queryKey: omnichannelQueryKeys.agents() });
2324
setUsername('');

apps/meteor/client/views/omnichannel/agents/hooks/useAgentsQuery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useQuery } from '@tanstack/react-query';
55
import { omnichannelQueryKeys } from '../../../../lib/queryKeys';
66

77
export const useAgentsQuery = (query: PaginatedRequest = {}) => {
8-
const getAgents = useEndpoint('GET', '/v1/livechat/users/agent');
8+
const getAgents = useEndpoint('GET', '/v1/livechat/users/:type', { type: 'agent' });
99

1010
return useQuery({
1111
queryKey: omnichannelQueryKeys.agents(query),

apps/meteor/client/views/omnichannel/agents/hooks/useRemoveAgent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const useRemoveAgent = (uid: ILivechatAgent['_id']) => {
1313
const queryClient = useQueryClient();
1414
const dispatchToastMessage = useToastMessageDispatch();
1515

16-
const deleteAction = useEndpoint('DELETE', '/v1/livechat/users/agent/:_id', { _id: uid });
16+
const deleteAction = useEndpoint('DELETE', '/v1/livechat/users/:type/:_id', { type: 'agent', _id: uid });
1717

1818
const handleDelete = useEffectEvent(() => {
1919
const onDeleteAgent = async () => {

apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/OutboundMessageWizard.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ const mockDepartmentAgent = {
4646

4747
const AppRoot = mockAppRoot()
4848
.withEndpoint('GET', '/v1/livechat/department', () => ({ departments: [mockDepartment], count: 1, offset: 0, total: 1 }))
49-
.withEndpoint('GET', '/v1/livechat/users/agent', () => ({ users: [{ ...mockAgent, departments: [] }], count: 1, offset: 0, total: 1 }))
49+
.withEndpoint('GET', '/v1/livechat/users/:type', () => ({ users: [{ ...mockAgent, departments: [] }], count: 1, offset: 0, total: 1 }))
5050
.withEndpoint('GET', '/v1/livechat/department/:_id', () => ({ department: mockDepartment, agents: [mockDepartmentAgent] }))
51-
.withEndpoint('GET', '/v1/livechat/users/agent/:_id', () => ({ user: mockAgent }))
51+
.withEndpoint('GET', '/v1/livechat/users/:type/:_id', () => ({ user: mockAgent }))
5252
.withEndpoint('GET', '/v1/omnichannel/outbound/providers', () => ({ providers: [providerMock] }))
5353
.withEndpoint('GET', '/v1/omnichannel/outbound/providers/:id/metadata', () => ({ metadata: providerMock }))
5454
.withEndpoint('GET', '/v1/omnichannel/contacts.get', () => ({ contact: contactMock }))

apps/meteor/client/views/omnichannel/components/outboundMessage/components/OutboundMessageWizard/steps/RepliesStep.stories.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@ const mockDepartmentAgent = {
4040

4141
const AppRoot = mockAppRoot()
4242
.withEndpoint('GET', '/v1/livechat/department', () => ({ departments: [mockDepartment], count: 1, offset: 0, total: 1 }))
43-
.withEndpoint('GET', '/v1/livechat/users/agent', () => ({ users: [{ ...mockAgent, departments: [] }], count: 1, offset: 0, total: 1 }))
43+
.withEndpoint('GET', '/v1/livechat/users/:type', () => ({ users: [{ ...mockAgent, departments: [] }], count: 1, offset: 0, total: 1 }))
4444
.withEndpoint('GET', '/v1/livechat/department/:_id', () => ({ department: mockDepartment, agents: [mockDepartmentAgent] }))
45-
.withEndpoint('GET', '/v1/livechat/users/agent/:_id', () => ({ user: mockAgent }))
45+
.withEndpoint('GET', '/v1/livechat/users/:type/:_id', () => ({ user: mockAgent }))
4646
.build();
4747

4848
const meta = {

apps/meteor/client/views/omnichannel/departments/DepartmentAgentsTable/AddAgent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function AddAgent({ agentList, onAdd, 'aria-labelledby': ariaLabelledBy }: AddAg
1919

2020
const [userId, setUserId] = useState('');
2121

22-
const { mutateAsync: getAgent } = useEndpointMutation('GET', '/v1/livechat/users/agent/:_id', { keys: { _id: userId } });
22+
const { mutateAsync: getAgent } = useEndpointMutation('GET', '/v1/livechat/users/:type/:_id', { keys: { type: 'agent', _id: userId } });
2323

2424
const dispatchToastMessage = useToastMessageDispatch();
2525

apps/meteor/client/views/omnichannel/hooks/useAgentsList.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const mockGetAgents = jest.fn();
1515

1616
const appRoot = new MockedAppRootBuilder()
1717
.withTranslations('en', 'core', { All: 'All', Empty_no_agent_selected: 'Empty, no agent selected' })
18-
.withEndpoint('GET', '/v1/livechat/users/agent', mockGetAgents);
18+
.withEndpoint('GET', '/v1/livechat/users/:type', mockGetAgents);
1919

2020
afterEach(() => {
2121
jest.clearAllMocks();

0 commit comments

Comments
 (0)