-
Notifications
You must be signed in to change notification settings - Fork 13.5k
chore: migrate livechat/config, livechat/webhook.test, and livechat/integrations.settings to OpenAPI chained API pattern with AJV validation #39506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
NAME-ASHWANIYADAV
wants to merge
8
commits into
RocketChat:develop
Choose a base branch
from
NAME-ASHWANIYADAV:chore/migrate-livechat-omnichannel-openapi
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e708897
chore:
NAME-ASHWANIYADAV 02b71a0
fix: replace schemas with inline types and fix lint issues
NAME-ASHWANIYADAV 73b247f
chore: add changeset for PR #39506
NAME-ASHWANIYADAV fcfc071
fix: resolve Typia schemas for ISetting and strict action match errors
NAME-ASHWANIYADAV d2193a3
fix: add ISetting to Typia as separate tuple element and restore $ref…
NAME-ASHWANIYADAV 86bcb3a
fix: restore guest in livechat/config response to match old contract
NAME-ASHWANIYADAV acefa43
fix: widen livechat/config response type and fix eslint warnings in w…
NAME-ASHWANIYADAV 8ae4f7d
Merge branch 'develop' into chore/migrate-livechat-omnichannel-openapi
NAME-ASHWANIYADAV File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@rocket.chat/meteor": patch | ||
| "@rocket.chat/core-typings": patch | ||
| "@rocket.chat/rest-typings": patch | ||
| --- | ||
|
|
||
| Migrates livechat/config, livechat/webhook.test, and livechat/integrations.settings API endpoints to the OpenAPI chained route definition pattern with AJV response validation and shared $ref schemas. |
44 changes: 40 additions & 4 deletions
44
apps/meteor/app/livechat/imports/server/rest/integrations.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,48 @@ | ||
| import type { ISetting } from '@rocket.chat/core-typings'; | ||
| import { schemas } from '@rocket.chat/core-typings'; | ||
| import { ajv, validateForbiddenErrorResponse, validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings'; | ||
|
|
||
| import { API } from '../../../../api/server'; | ||
| import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass'; | ||
| import { findIntegrationSettings } from '../../../server/api/lib/integrations'; | ||
|
|
||
| API.v1.addRoute( | ||
| // Register ISetting schema for $ref resolution (livechat loads before api/server/ajv.ts) | ||
| const iSettingSchema = schemas.components?.schemas?.ISetting; | ||
| if (iSettingSchema && !ajv.getSchema('#/components/schemas/ISetting')) { | ||
| ajv.addSchema(iSettingSchema, '#/components/schemas/ISetting'); | ||
| } | ||
|
|
||
| const livechatIntegrationsEndpoints = API.v1.get( | ||
| 'livechat/integrations.settings', | ||
| { authRequired: true, permissionsRequired: ['view-livechat-manager'] }, | ||
| { | ||
| async get() { | ||
| return API.v1.success(await findIntegrationSettings()); | ||
| authRequired: true, | ||
| permissionsRequired: ['view-livechat-manager'], | ||
| query: undefined, | ||
| response: { | ||
NAME-ASHWANIYADAV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 401: validateUnauthorizedErrorResponse, | ||
| 403: validateForbiddenErrorResponse, | ||
| 200: ajv.compile<{ settings: ISetting[]; success: boolean }>({ | ||
| type: 'object', | ||
| properties: { | ||
| settings: { | ||
| type: 'array', | ||
| items: { $ref: '#/components/schemas/ISetting' }, | ||
| }, | ||
NAME-ASHWANIYADAV marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| success: { type: 'boolean', enum: [true] }, | ||
| }, | ||
| required: ['settings', 'success'], | ||
| additionalProperties: false, | ||
| }), | ||
| }, | ||
| }, | ||
| async function action() { | ||
| return API.v1.success(await findIntegrationSettings()); | ||
| }, | ||
| ); | ||
|
|
||
| type LivechatIntegrationsEndpoints = ExtractRoutesFromAPI<typeof livechatIntegrationsEndpoints>; | ||
|
|
||
| declare module '@rocket.chat/rest-typings' { | ||
| // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface | ||
| interface Endpoints extends LivechatIntegrationsEndpoints {} | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,95 +1,115 @@ | ||
| import { Logger } from '@rocket.chat/logger'; | ||
| import { ajv, validateUnauthorizedErrorResponse } from '@rocket.chat/rest-typings'; | ||
| import type { ExtendedFetchOptions } from '@rocket.chat/server-fetch'; | ||
| import { serverFetch as fetch } from '@rocket.chat/server-fetch'; | ||
|
|
||
| import { API } from '../../../../api/server'; | ||
| import type { ExtractRoutesFromAPI } from '../../../../api/server/ApiClass'; | ||
| import { settings } from '../../../../settings/server'; | ||
|
|
||
| const logger = new Logger('WebhookTest'); | ||
|
|
||
| API.v1.addRoute( | ||
| const livechatWebhookEndpoints = API.v1.post( | ||
| 'livechat/webhook.test', | ||
| { authRequired: true, permissionsRequired: ['view-livechat-webhooks'] }, | ||
| { | ||
| async post() { | ||
| const sampleData = { | ||
| type: 'LivechatSession', | ||
| _id: 'fasd6f5a4sd6f8a4sdf', | ||
| label: 'title', | ||
| topic: 'asiodojf', | ||
| createdAt: new Date(), | ||
| lastMessageAt: new Date(), | ||
| tags: ['tag1', 'tag2', 'tag3'], | ||
| authRequired: true, | ||
| permissionsRequired: ['view-livechat-webhooks'], | ||
| response: { | ||
| 401: validateUnauthorizedErrorResponse, | ||
| 200: ajv.compile<{ success: boolean }>({ | ||
| type: 'object', | ||
| properties: { | ||
| success: { type: 'boolean', enum: [true] }, | ||
| }, | ||
| required: ['success'], | ||
| additionalProperties: false, | ||
| }), | ||
| }, | ||
| }, | ||
| async function action() { | ||
| const sampleData = { | ||
| type: 'LivechatSession', | ||
| _id: 'fasd6f5a4sd6f8a4sdf', | ||
| label: 'title', | ||
| topic: 'asiodojf', | ||
| createdAt: new Date(), | ||
| lastMessageAt: new Date(), | ||
| tags: ['tag1', 'tag2', 'tag3'], | ||
| customFields: { | ||
| productId: '123456', | ||
| }, | ||
| visitor: { | ||
| _id: '', | ||
| name: 'visitor name', | ||
| username: 'visitor-username', | ||
| department: 'department', | ||
| email: 'email@address.com', | ||
| phone: '192873192873', | ||
| ip: '123.456.7.89', | ||
| browser: 'Chrome', | ||
| os: 'Linux', | ||
| customFields: { | ||
| productId: '123456', | ||
| customerId: '123456', | ||
| }, | ||
| visitor: { | ||
| _id: '', | ||
| name: 'visitor name', | ||
| }, | ||
| agent: { | ||
| _id: 'asdf89as6df8', | ||
| username: 'agent.username', | ||
| name: 'Agent Name', | ||
| email: 'agent@email.com', | ||
| }, | ||
| messages: [ | ||
| { | ||
| username: 'visitor-username', | ||
| department: 'department', | ||
| email: 'email@address.com', | ||
| phone: '192873192873', | ||
| ip: '123.456.7.89', | ||
| browser: 'Chrome', | ||
| os: 'Linux', | ||
| customFields: { | ||
| customerId: '123456', | ||
| }, | ||
| msg: 'message content', | ||
| ts: new Date(), | ||
| }, | ||
| agent: { | ||
| _id: 'asdf89as6df8', | ||
| { | ||
| username: 'agent.username', | ||
| name: 'Agent Name', | ||
| email: 'agent@email.com', | ||
| agentId: 'asdf89as6df8', | ||
| msg: 'message content from agent', | ||
| ts: new Date(), | ||
| }, | ||
| messages: [ | ||
| { | ||
| username: 'visitor-username', | ||
| msg: 'message content', | ||
| ts: new Date(), | ||
| }, | ||
| { | ||
| username: 'agent.username', | ||
| agentId: 'asdf89as6df8', | ||
| msg: 'message content from agent', | ||
| ts: new Date(), | ||
| }, | ||
| ], | ||
| }; | ||
| const options = { | ||
| method: 'POST', | ||
| headers: { | ||
| 'X-RocketChat-Livechat-Token': settings.get<string>('Livechat_secret_token'), | ||
| 'Accept': 'application/json', | ||
| }, | ||
| body: sampleData, | ||
| // SECURITY: Webhooks can only be configured by users with enough privileges. It's ok to disable this check here. | ||
| ignoreSsrfValidation: true, | ||
| size: 10 * 1024 * 1024, | ||
| } as ExtendedFetchOptions; | ||
|
|
||
| const webhookUrl = settings.get<string>('Livechat_webhookUrl'); | ||
| ], | ||
| }; | ||
| const options = { | ||
| method: 'POST', | ||
| headers: { | ||
| 'X-RocketChat-Livechat-Token': settings.get<string>('Livechat_secret_token'), | ||
| 'Accept': 'application/json', | ||
| }, | ||
| body: sampleData, | ||
| ignoreSsrfValidation: true, | ||
| size: 10 * 1024 * 1024, | ||
| } as ExtendedFetchOptions; | ||
|
|
||
| if (!webhookUrl) { | ||
| return API.v1.failure('Webhook_URL_not_set'); | ||
| } | ||
| const webhookUrl = settings.get<string>('Livechat_webhookUrl'); | ||
|
|
||
| try { | ||
| logger.debug({ msg: 'Testing webhook', webhookUrl }); | ||
| const request = await fetch(webhookUrl, options); | ||
| const response = await request.text(); | ||
| if (!webhookUrl) { | ||
| return API.v1.failure('Webhook_URL_not_set'); | ||
| } | ||
|
|
||
| logger.debug({ msg: 'Webhook response', response }); | ||
| if (request.status === 200) { | ||
| return API.v1.success(); | ||
| } | ||
| try { | ||
| logger.debug({ msg: 'Testing webhook', host: new URL(webhookUrl).host }); | ||
| const request = await fetch(webhookUrl, options); | ||
| await request.text(); | ||
|
|
||
| throw new Error('Invalid status code'); | ||
| } catch (error) { | ||
| logger.error({ msg: 'Error testing webhook', err: error }); | ||
| throw new Error('error-invalid-webhook-response'); | ||
| logger.debug({ msg: 'Webhook response', status: request.status }); | ||
| if (request.status === 200) { | ||
| return API.v1.success(); | ||
| } | ||
| }, | ||
|
|
||
| throw new Error('Invalid status code'); | ||
| } catch (error) { | ||
| logger.error({ msg: 'Error testing webhook', err: error }); | ||
| throw new Error('error-invalid-webhook-response'); | ||
| } | ||
| }, | ||
| ); | ||
|
|
||
| type LivechatWebhookEndpoints = ExtractRoutesFromAPI<typeof livechatWebhookEndpoints>; | ||
|
|
||
| declare module '@rocket.chat/rest-typings' { | ||
| // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-empty-interface | ||
| interface Endpoints extends LivechatWebhookEndpoints {} | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.