diff --git a/apps/api/src/common/validators/token-validator.decorator.spec.ts b/apps/api/src/common/validators/token-validator.decorator.spec.ts new file mode 100644 index 000000000..5753e7d85 --- /dev/null +++ b/apps/api/src/common/validators/token-validator.decorator.spec.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { IsNotEmpty, validate } from 'class-validator'; + +import { TokenValidator } from './token-validator'; + +class TokenDto { + @IsNotEmpty() + @TokenValidator({ message: 'Invalid token format' }) + token: string; +} + +describe('TokenValidator', () => { + it('should validate a correct token', async () => { + const dto = new TokenDto(); + dto.token = 'validToken123456'; + + const errors = await validate(dto); + expect(errors.length).toBe(0); + }); + + it('should invalidate a token with invalid characters', async () => { + const dto = new TokenDto(); + dto.token = 'invalidToken$123'; + + const errors = await validate(dto); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].constraints).toHaveProperty('TokenValidatorConstraint'); + }); + + it('should invalidate a token that is too short', async () => { + const dto = new TokenDto(); + dto.token = 'short'; + + const errors = await validate(dto); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].constraints).toHaveProperty('TokenValidatorConstraint'); + }); + + it('should invalidate an empty token', async () => { + const dto = new TokenDto(); + dto.token = ''; + + const errors = await validate(dto); + expect(errors.length).toBeGreaterThan(0); + expect(errors[0].constraints).toHaveProperty('isNotEmpty'); + }); +}); diff --git a/apps/api/src/common/validators/token-validator.ts b/apps/api/src/common/validators/token-validator.ts new file mode 100644 index 000000000..f4ea434a9 --- /dev/null +++ b/apps/api/src/common/validators/token-validator.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { + registerDecorator, + ValidationOptions, + ValidatorConstraint, + ValidatorConstraintInterface, +} from 'class-validator'; + +@ValidatorConstraint({ async: false }) +export class TokenValidatorConstraint implements ValidatorConstraintInterface { + validate(token: string | null) { + const regex = /^[a-zA-Z0-9._-]+$/; + return ( + !token || + (typeof token === 'string' && regex.test(token) && token.length >= 16) + ); + } + + defaultMessage() { + return 'Token must be at least 16 characters long and contain only alphanumeric characters, dots, hyphens, and underscores.'; + } +} + +export function TokenValidator(validationOptions?: ValidationOptions) { + return function (object: object, propertyName: string) { + registerDecorator({ + target: object.constructor, + propertyName: propertyName, + options: validationOptions, + constraints: [], + validator: TokenValidatorConstraint, + }); + }; +} diff --git a/apps/api/src/configs/modules/typeorm-config/migrations/1720760282371-add-token-on-webhook.ts b/apps/api/src/configs/modules/typeorm-config/migrations/1720760282371-add-token-on-webhook.ts new file mode 100644 index 000000000..64e2c50c0 --- /dev/null +++ b/apps/api/src/configs/modules/typeorm-config/migrations/1720760282371-add-token-on-webhook.ts @@ -0,0 +1,30 @@ +/** + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddTokenOnWebhook1720760282371 implements MigrationInterface { + name = 'AddTokenOnWebhook1720760282371'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`webhooks\` ADD \`token\` varchar(255) NULL DEFAULT NULL AFTER \`url\``, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`webhooks\` DROP COLUMN \`token\``); + } +} diff --git a/apps/api/src/domains/admin/project/webhook/dtos/create-webhook.dto.ts b/apps/api/src/domains/admin/project/webhook/dtos/create-webhook.dto.ts index a92af7422..aea889f72 100644 --- a/apps/api/src/domains/admin/project/webhook/dtos/create-webhook.dto.ts +++ b/apps/api/src/domains/admin/project/webhook/dtos/create-webhook.dto.ts @@ -34,6 +34,9 @@ export class CreateWebhookDto { @Expose() events: EventDto[]; + @Expose() + token: string | null; + public static from(params: any): CreateWebhookDto { return plainToInstance(CreateWebhookDto, params, { excludeExtraneousValues: true, diff --git a/apps/api/src/domains/admin/project/webhook/dtos/requests/create-webhook-request.dto.ts b/apps/api/src/domains/admin/project/webhook/dtos/requests/create-webhook-request.dto.ts index 7e0038444..6e2496a8f 100644 --- a/apps/api/src/domains/admin/project/webhook/dtos/requests/create-webhook-request.dto.ts +++ b/apps/api/src/domains/admin/project/webhook/dtos/requests/create-webhook-request.dto.ts @@ -17,6 +17,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsArray, IsEnum, IsString } from 'class-validator'; import { WebhookStatusEnum } from '@/common/enums'; +import { TokenValidator } from '@/common/validators/token-validator'; +import { IsNullable } from '@/domains/admin/user/decorators'; import { EventDto } from '..'; export class CreateWebhookRequestDto { @@ -35,4 +37,9 @@ export class CreateWebhookRequestDto { @ApiProperty({ type: [EventDto] }) @IsArray() events: EventDto[]; + + @ApiProperty({ nullable: true }) + @IsNullable() + @TokenValidator() + token: string | null; } diff --git a/apps/api/src/domains/admin/project/webhook/dtos/responses/get-webhook-by-id-response.dto.ts b/apps/api/src/domains/admin/project/webhook/dtos/responses/get-webhook-by-id-response.dto.ts index b4021f8a2..49c37a67f 100644 --- a/apps/api/src/domains/admin/project/webhook/dtos/responses/get-webhook-by-id-response.dto.ts +++ b/apps/api/src/domains/admin/project/webhook/dtos/responses/get-webhook-by-id-response.dto.ts @@ -58,6 +58,10 @@ export class GetWebhookByIdResponseDto { @ApiProperty() url: string; + @Expose() + @ApiProperty() + token: string; + @Expose() @ApiProperty({ type: WebhookStatusEnum, enum: WebhookStatusEnum }) status: WebhookStatusEnum; diff --git a/apps/api/src/domains/admin/project/webhook/webhook.entity.ts b/apps/api/src/domains/admin/project/webhook/webhook.entity.ts index a8092f96a..6dfbdbad5 100644 --- a/apps/api/src/domains/admin/project/webhook/webhook.entity.ts +++ b/apps/api/src/domains/admin/project/webhook/webhook.entity.ts @@ -28,6 +28,9 @@ export class WebhookEntity extends CommonEntity { @Column('varchar') url: string; + @Column('varchar') + token: string | null; + @Column('enum', { enum: WebhookStatusEnum, default: WebhookStatusEnum.ACTIVE, @@ -45,12 +48,13 @@ export class WebhookEntity extends CommonEntity { }) events: Relation[]; - static from({ projectId, name, url, status, events }) { + static from({ projectId, name, url, token, status, events }) { const webhook = new WebhookEntity(); webhook.project = new ProjectEntity(); webhook.project.id = projectId; webhook.name = name; webhook.url = url; + webhook.token = token; webhook.status = status; webhook.events = events; diff --git a/apps/api/src/domains/admin/project/webhook/webhook.listener.spec.ts b/apps/api/src/domains/admin/project/webhook/webhook.listener.spec.ts index 98557744f..610a13c87 100644 --- a/apps/api/src/domains/admin/project/webhook/webhook.listener.spec.ts +++ b/apps/api/src/domains/admin/project/webhook/webhook.listener.spec.ts @@ -59,6 +59,7 @@ describe('webhook listener', () => { { headers: { 'Content-Type': 'application/json', + 'x-webhook-token': 'TEST-TOKEN', }, }, ); @@ -87,6 +88,7 @@ describe('webhook listener', () => { { headers: { 'Content-Type': 'application/json', + 'x-webhook-token': 'TEST-TOKEN', }, }, ); @@ -114,6 +116,7 @@ describe('webhook listener', () => { { headers: { 'Content-Type': 'application/json', + 'x-webhook-token': 'TEST-TOKEN', }, }, ); @@ -142,6 +145,7 @@ describe('webhook listener', () => { { headers: { 'Content-Type': 'application/json', + 'x-webhook-token': 'TEST-TOKEN', }, }, ); diff --git a/apps/api/src/domains/admin/project/webhook/webhook.listener.ts b/apps/api/src/domains/admin/project/webhook/webhook.listener.ts index b11e93bdc..382038d1f 100644 --- a/apps/api/src/domains/admin/project/webhook/webhook.listener.ts +++ b/apps/api/src/domains/admin/project/webhook/webhook.listener.ts @@ -18,7 +18,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { InjectRepository } from '@nestjs/typeorm'; import type { AxiosError } from 'axios'; -import { catchError, lastValueFrom, of } from 'rxjs'; +import { catchError, lastValueFrom, of, retry, timer } from 'rxjs'; import { Repository } from 'typeorm'; import type { IssueStatusEnum } from '@/common/enums'; @@ -63,10 +63,20 @@ export class WebhookListener { { headers: { 'Content-Type': 'application/json', + 'x-webhook-token': webhook.token, }, }, ) .pipe( + retry({ + count: 3, + delay: (error, retryCount) => { + this.logger.warn( + `Retrying webhook... Attempt #${retryCount + 1}`, + ); + return timer(3000); + }, + }), catchError((error: AxiosError) => { this.logger.error({ message: 'Failed to send webhook', diff --git a/apps/api/src/domains/admin/project/webhook/webhook.service.spec.ts b/apps/api/src/domains/admin/project/webhook/webhook.service.spec.ts index a9e0a2177..050258355 100644 --- a/apps/api/src/domains/admin/project/webhook/webhook.service.spec.ts +++ b/apps/api/src/domains/admin/project/webhook/webhook.service.spec.ts @@ -39,6 +39,7 @@ function createCreateWebhookDto(overrides = {}): CreateWebhookDto { projectId: webhookFixture.project.id, name: faker.string.sample(), url: faker.internet.url(), + token: faker.string.sample(), status: WebhookStatusEnum.ACTIVE, events: [ { @@ -72,6 +73,7 @@ function createUpdateWebhookDto(overrides = {}): UpdateWebhookDto { projectId: webhookFixture.project.id, name: faker.string.sample(), url: faker.internet.url(), + token: faker.string.sample(), status: getRandomEnumValue(WebhookStatusEnum), events: [ { @@ -124,6 +126,7 @@ describe('webhook service', () => { expect(webhook.project.id).toBe(dto.projectId); expect(webhook.name).toBe(dto.name); expect(webhook.url).toBe(dto.url); + expect(webhook.token).toBe(dto.token); expect(webhook.status).toBe(dto.status); expect(webhook.events.length).toBe(dto.events.length); for (let i = 0; i < webhook.events.length; i++) { @@ -163,6 +166,7 @@ describe('webhook service', () => { expect(webhook.project.id).toBe(dto.projectId); expect(webhook.name).toBe(dto.name); expect(webhook.url).toBe(dto.url); + expect(webhook.token).toBe(dto.token); expect(webhook.status).toBe(dto.status); expect(webhook.events.length).toBe(dto.events.length); expect(webhook.events[0].channels.length).toBe( @@ -273,6 +277,7 @@ describe('webhook service', () => { expect(webhook.project.id).toBe(dto.projectId); expect(webhook.name).toBe(dto.name); expect(webhook.url).toBe(dto.url); + expect(webhook.token).toBe(dto.token); expect(webhook.status).toBe(dto.status); expect(webhook.events.length).toBe(dto.events.length); for (let i = 0; i < webhook.events.length; i++) { diff --git a/apps/api/src/domains/admin/project/webhook/webhook.service.ts b/apps/api/src/domains/admin/project/webhook/webhook.service.ts index 70a61f67a..cda89dba8 100644 --- a/apps/api/src/domains/admin/project/webhook/webhook.service.ts +++ b/apps/api/src/domains/admin/project/webhook/webhook.service.ts @@ -92,6 +92,7 @@ export class WebhookService { projectId: dto.projectId, name: dto.name, url: dto.url, + token: dto.token, status: dto.status, events, }); @@ -137,6 +138,7 @@ export class WebhookService { webhook.name = dto.name; webhook.url = dto.url; + webhook.token = dto.token; webhook.status = dto.status; webhook.events = ( await Promise.all( diff --git a/apps/api/src/test-utils/fixtures.ts b/apps/api/src/test-utils/fixtures.ts index 08c1b761c..e96dde4af 100644 --- a/apps/api/src/test-utils/fixtures.ts +++ b/apps/api/src/test-utils/fixtures.ts @@ -358,6 +358,7 @@ export const webhookFixture = { id: faker.number.int(), name: faker.string.sample(), url: faker.internet.url(), + token: 'TEST-TOKEN', status: WebhookStatusEnum.ACTIVE, project: projectFixture, events: getAllEvents(), diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js index 9024f4d9f..b2f7c18c9 100644 --- a/apps/web/eslint.config.js +++ b/apps/web/eslint.config.js @@ -12,6 +12,7 @@ module.exports = [ 'jest.setup.ts', 'next-env.d.ts', 'jest.polyfills.js', + '**/api.type.ts', ], }, ...baseConfig, diff --git a/apps/web/public/locales/de/common.json b/apps/web/public/locales/de/common.json index e7506b475..8fcb972cf 100644 --- a/apps/web/public/locales/de/common.json +++ b/apps/web/public/locales/de/common.json @@ -252,7 +252,8 @@ "next": "Nächste", "complete": "Abschließen", "next-time": "Später", - "start": "Starten" + "start": "Starten", + "generate": "Generieren" }, "input": { "label": { diff --git a/apps/web/public/locales/en/common.json b/apps/web/public/locales/en/common.json index 16e636303..1dff08d54 100644 --- a/apps/web/public/locales/en/common.json +++ b/apps/web/public/locales/en/common.json @@ -252,7 +252,8 @@ "next": "Next", "complete": "Complete", "next-time": "Later", - "start": "Start" + "start": "Start", + "generate": "Generate" }, "input": { "label": { diff --git a/apps/web/public/locales/ja/common.json b/apps/web/public/locales/ja/common.json index dd1b494dd..a7fac8c8e 100644 --- a/apps/web/public/locales/ja/common.json +++ b/apps/web/public/locales/ja/common.json @@ -240,7 +240,8 @@ "next": "次へ", "complete": "完了", "next-time": "次回", - "start": "はじまり" + "start": "はじまり", + "generate": "生成" }, "input": { "label": { diff --git a/apps/web/public/locales/ko/common.json b/apps/web/public/locales/ko/common.json index 61c9531c8..6311ed2c4 100644 --- a/apps/web/public/locales/ko/common.json +++ b/apps/web/public/locales/ko/common.json @@ -240,7 +240,8 @@ "next": "다음", "complete": "완료", "next-time": "다음에", - "start": "시작하기" + "start": "시작하기", + "generate": "생성" }, "input": { "label": { diff --git a/apps/web/public/locales/zh/common.json b/apps/web/public/locales/zh/common.json index 48f1a27d9..8fa04e5d6 100644 --- a/apps/web/public/locales/zh/common.json +++ b/apps/web/public/locales/zh/common.json @@ -252,7 +252,8 @@ "next": "下一个", "complete": "完成", "next-time": "下次", - "start": "开始" + "start": "开始", + "generate": "產生" }, "input": { "label": { diff --git a/apps/web/src/entities/webhook/ui/create-webhook-popover.tsx b/apps/web/src/entities/webhook/ui/create-webhook-popover.tsx index 857e78b8b..f08b791ba 100644 --- a/apps/web/src/entities/webhook/ui/create-webhook-popover.tsx +++ b/apps/web/src/entities/webhook/ui/create-webhook-popover.tsx @@ -30,6 +30,7 @@ const defaultValues: WebhookInfo = { name: '', status: 'ACTIVE', url: '', + token: null, events: [ { type: 'FEEDBACK_CREATION', channelIds: [], status: 'INACTIVE' }, { type: 'ISSUE_ADDITION', channelIds: [], status: 'INACTIVE' }, diff --git a/apps/web/src/entities/webhook/ui/update-webhook-popover.tsx b/apps/web/src/entities/webhook/ui/update-webhook-popover.tsx index 4a1459bab..19ac67e2e 100644 --- a/apps/web/src/entities/webhook/ui/update-webhook-popover.tsx +++ b/apps/web/src/entities/webhook/ui/update-webhook-popover.tsx @@ -98,6 +98,7 @@ const convertDefatulValuesToFormValues = (defaultValues: Webhook) => { name: defaultValues.name, url: defaultValues.url, status: defaultValues.status, + token: defaultValues.token, events: defaultValues.events.map((event) => ({ status: event.status, type: event.type, diff --git a/apps/web/src/entities/webhook/ui/webhook-form.ui.tsx b/apps/web/src/entities/webhook/ui/webhook-form.ui.tsx index 2e4a5a5dc..14fc6f0e1 100644 --- a/apps/web/src/entities/webhook/ui/webhook-form.ui.tsx +++ b/apps/web/src/entities/webhook/ui/webhook-form.ui.tsx @@ -33,7 +33,9 @@ interface IProps { const WebhookForm: React.FC = (props) => { const { channels } = props; + const { t } = useTranslation(); + const { register, setValue, getValues, watch, formState } = useFormContext(); @@ -107,10 +109,28 @@ const WebhookForm: React.FC = (props) => { {...register('url')} isSubmitted={formState.isSubmitted} isSubmitting={formState.isSubmitting} - isValid={!formState.errors.name} + isValid={!formState.errors.url} hint={formState.errors.url?.message} required /> + setValue('token', window.crypto.randomUUID())} + > + {t('button.generate')} + + } + />

Event

diff --git a/apps/web/src/entities/webhook/webhook.schema.ts b/apps/web/src/entities/webhook/webhook.schema.ts index 328810675..f46c4ef39 100644 --- a/apps/web/src/entities/webhook/webhook.schema.ts +++ b/apps/web/src/entities/webhook/webhook.schema.ts @@ -36,6 +36,17 @@ export const webhookSchema = z.object({ url: z.string(), status: z.enum(['ACTIVE', 'INACTIVE']), events: z.array(webhookEventSchema), + token: z + .string() + .min(16) + .regex(/^[a-zA-Z0-9._-]+$/) + .or( + z + .string() + .length(0) + .transform(() => null), + ) + .or(z.null()), createdAt: z.string(), }); diff --git a/apps/web/src/shared/types/api.type.ts b/apps/web/src/shared/types/api.type.ts index 896e5c780..5c5e9df50 100644 --- a/apps/web/src/shared/types/api.type.ts +++ b/apps/web/src/shared/types/api.type.ts @@ -1,17 +1,6 @@ /** - * Copyright 2023 LINE Corporation - * - * LINE Corporation licenses this file to you under the Apache License, - * version 2.0 (the "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at: - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. */ export interface paths { @@ -1882,6 +1871,7 @@ export interface components { /** @enum {string} */ status: 'ACTIVE' | 'INACTIVE'; events: components['schemas']['EventDto'][]; + token: string | null; }; CreateWebhookResponseDto: { id: number; @@ -1904,6 +1894,7 @@ export interface components { id: number; name: string; url: string; + token: string; /** @enum {string} */ status: 'ACTIVE' | 'INACTIVE'; events: components['schemas']['GetWebhookResponseEventDto'][]; @@ -1919,6 +1910,7 @@ export interface components { /** @enum {string} */ status: 'ACTIVE' | 'INACTIVE'; events: components['schemas']['EventDto'][]; + token: string | null; }; UpdateWebhookResponseDto: { id: number; @@ -1946,7 +1938,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['SendEmailCodeResponseDto']; }; @@ -1967,7 +1961,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -1986,7 +1982,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2005,7 +2003,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2024,7 +2024,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2043,7 +2045,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['SignInResponseDto']; }; @@ -2062,7 +2066,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['OAuthLoginUrlResponseDto']; }; @@ -2079,7 +2085,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2094,7 +2102,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['SignInResponseDto']; }; @@ -2116,7 +2126,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetAllUserResponseDto']; }; @@ -2137,7 +2149,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2156,7 +2170,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetAllUserResponseDto']; }; @@ -2175,7 +2191,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['UserDto']; }; @@ -2198,7 +2216,9 @@ export interface operations { }; responses: { 204: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2215,7 +2235,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2232,7 +2254,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetRolesByIdResponseDto']; }; @@ -2253,7 +2277,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2272,7 +2298,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2291,7 +2319,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2310,7 +2340,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2325,7 +2357,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetTenantResponseDto']; }; @@ -2346,7 +2380,9 @@ export interface operations { }; responses: { 204: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2365,7 +2401,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2382,7 +2420,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CountFeedbacksByTenantIdResponseDto']; }; @@ -2401,7 +2441,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetAllRolesResponseDto']; }; @@ -2424,7 +2466,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2446,7 +2490,9 @@ export interface operations { }; responses: { 204: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2464,7 +2510,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2483,7 +2531,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetAllMemberResponseDto']; }; @@ -2506,7 +2556,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2528,7 +2580,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2546,7 +2600,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2563,7 +2619,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindApiKeysResponseDto']; }; @@ -2586,7 +2644,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateApiKeyResponseDto']; }; @@ -2605,7 +2665,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2622,7 +2684,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2639,7 +2703,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2662,7 +2728,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindChannelsByProjectIdResponseDto']; }; @@ -2685,7 +2753,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateChannelResponseDto']; }; @@ -2706,7 +2776,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': boolean; }; @@ -2726,7 +2798,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindChannelByIdResponseDto']; }; @@ -2750,7 +2824,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2768,7 +2844,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2790,7 +2868,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2811,7 +2891,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['ImageUploadUrlTestResponseDto']; }; @@ -2830,7 +2912,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindOptionByFieldIdResponseDto'][]; }; @@ -2853,7 +2937,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateOptionResponseDto']; }; @@ -2876,7 +2962,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindProjectsResponseDto']; }; @@ -2897,7 +2985,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateProjectResponseDto']; }; @@ -2916,7 +3006,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2933,7 +3025,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindProjectByIdResponseDto']; }; @@ -2956,7 +3050,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['UpdateProjectResponseDto']; }; @@ -2975,7 +3071,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -2992,7 +3090,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CountFeedbacksByIdResponseDto']; }; @@ -3011,7 +3111,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CountIssuesByIdResponseDto']; }; @@ -3031,7 +3133,9 @@ export interface operations { requestBody?: never; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3053,7 +3157,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3075,7 +3181,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindFeedbacksByChannelIdResponseDto']; }; @@ -3097,7 +3205,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['AddIssueResponseDto']; }; @@ -3119,7 +3229,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['AddIssueResponseDto']; }; @@ -3143,7 +3255,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3162,7 +3276,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3183,7 +3299,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateIssueResponseDto']; }; @@ -3206,7 +3324,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3224,7 +3344,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindIssueByIdResponseDto'][]; }; @@ -3248,7 +3370,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3266,7 +3390,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content?: never; }; }; @@ -3287,7 +3413,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindIssuesByProjectIdResponseDto']; }; @@ -3308,7 +3436,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountResponseDto']; }; @@ -3330,7 +3460,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountByDateResponseDto']; }; @@ -3349,7 +3481,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountByStatusResponseDto']; }; @@ -3371,7 +3505,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountByDateByChannelResponseDto']; }; @@ -3392,7 +3528,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountResponseDto']; }; @@ -3413,7 +3551,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindIssuedRateResponseDto']; }; @@ -3435,7 +3575,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindCountByDateByIssueResponseDto']; }; @@ -3454,7 +3596,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['FindIssueTrackerResponseDto']; }; @@ -3477,7 +3621,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['UpdateIssueTrackerResponseDto']; }; @@ -3500,7 +3646,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateIssueTrackerResponseDto']; }; @@ -3519,7 +3667,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetWebhooksByProjectIdResponseDto']; }; @@ -3542,7 +3692,9 @@ export interface operations { }; responses: { 201: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['CreateWebhookResponseDto']; }; @@ -3561,7 +3713,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetWebhookByIdResponseDto']; }; @@ -3585,7 +3739,9 @@ export interface operations { }; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['UpdateWebhookResponseDto']; }; @@ -3604,7 +3760,9 @@ export interface operations { requestBody?: never; responses: { 200: { - headers: Record; + headers: { + [name: string]: unknown; + }; content: { 'application/json': components['schemas']['GetWebhookByIdResponseDto']; };