feat: add organization routing forms endpoints [v2] (#20580)
* feat: add organization routing forms endpoints Co-Authored-By: morgan@cal.com <morgan@cal.com> * refactor: reorganize team routing forms modules and add new endpoints * fix: routing form responses and updated at * fix: OrganizationsRoutingFormController e2e test * refactor and fix stuff * remove not needed files and fix comments * fix OrganizationsTeamsRoutingFormsResponsesController * fix tests * fix tests * fix: get routing form output * remove log * remove now * fix getTeamRoutingForms types * refactor: update routing forms API endpoints and repository methods * added `teamIds?: number[]` filter * Update get-routing-form-responses-params.input.ts * Update get-routing-form-responses-params.input.ts * fix e2e * add Patch team routing form responses e2e --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: morgan@cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
This commit is contained in:
34 files changed
+3304
-100
No files matched your search
@@ -5,6 +5,7 @@ import { ConferencingModule } from "@/modules/conferencing/conferencing.module";
|
||||
import { DestinationCalendarsModule } from "@/modules/destination-calendars/destination-calendars.module";
|
||||
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
|
||||
import { OrganizationsBookingsModule } from "@/modules/organizations/bookings/organizations.bookings.module";
|
||||
import { OrganizationsRoutingFormsModule } from "@/modules/organizations/routing-forms/organizations-routing-forms.module";
|
||||
import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/teams/bookings/organizations-teams-bookings.module";
|
||||
import { OrganizationsUsersBookingsModule } from "@/modules/organizations/users/bookings/organizations-users-bookings.module";
|
||||
import { RouterModule } from "@/modules/router/router.module";
|
||||
@@ -31,6 +32,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
|
||||
OrganizationsTeamsBookingsModule,
|
||||
OrganizationsUsersBookingsModule,
|
||||
OrganizationsBookingsModule,
|
||||
OrganizationsRoutingFormsModule,
|
||||
RouterModule,
|
||||
],
|
||||
})
|
||||
|
||||
@@ -37,7 +37,7 @@ import { OrganizationsTeamsService } from "@/modules/organizations/teams/index/s
|
||||
import { OrganizationsTeamsMembershipsController } from "@/modules/organizations/teams/memberships/organizations-teams-memberships.controller";
|
||||
import { OrganizationsTeamsMembershipsRepository } from "@/modules/organizations/teams/memberships/organizations-teams-memberships.repository";
|
||||
import { OrganizationsTeamsMembershipsService } from "@/modules/organizations/teams/memberships/services/organizations-teams-memberships.service";
|
||||
import { OrganizationsTeamsRoutingFormsModule } from "@/modules/organizations/teams/routing-forms/organizations-teams-routing-forms-responses.module";
|
||||
import { OrganizationsTeamsRoutingFormsModule } from "@/modules/organizations/teams/routing-forms/organizations-teams-routing-forms.module";
|
||||
import { OrganizationsTeamsSchedulesController } from "@/modules/organizations/teams/schedules/organizations-teams-schedules.controller";
|
||||
import { OrganizationsUsersController } from "@/modules/organizations/users/index/controllers/organizations-users.controller";
|
||||
import { OrganizationsUsersRepository } from "@/modules/organizations/users/index/organizations-users.repository";
|
||||
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { App_RoutingForms_Form, App_RoutingForms_FormResponse, Team, User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
describe("OrganizationsRoutingFormsResponsesController", () => {
|
||||
let app: INestApplication;
|
||||
let prismaWriteService: PrismaWriteService;
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
let apiKeyString: string;
|
||||
let routingForm: App_RoutingForms_Form;
|
||||
let routingFormResponse: App_RoutingForms_FormResponse;
|
||||
let routingFormResponse2: App_RoutingForms_FormResponse;
|
||||
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
|
||||
let user: User;
|
||||
const userEmail = `OrganizationsRoutingFormsResponsesController-key-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
}).compile();
|
||||
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
|
||||
prismaWriteService = moduleRef.get<PrismaWriteService>(PrismaWriteService);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `OrganizationsRoutingFormsResponsesController-teams-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "OrganizationsRoutingFormsResponsesController orgs booking 1",
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${user.id}`,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = `${keyString}`;
|
||||
|
||||
routingForm = await prismaWriteService.prisma.app_RoutingForms_Form.create({
|
||||
data: {
|
||||
name: "Test Routing Form",
|
||||
description: "Test Description",
|
||||
disabled: false,
|
||||
routes: JSON.stringify([]),
|
||||
fields: JSON.stringify([]),
|
||||
settings: JSON.stringify({}),
|
||||
teamId: team.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
routingFormResponse = await prismaWriteService.prisma.app_RoutingForms_FormResponse.create({
|
||||
data: {
|
||||
formId: routingForm.id,
|
||||
response: JSON.stringify({ question1: "answer1", question2: "answer2" }),
|
||||
},
|
||||
});
|
||||
|
||||
routingFormResponse2 = await prismaWriteService.prisma.app_RoutingForms_FormResponse.create({
|
||||
data: {
|
||||
formId: routingForm.id,
|
||||
response: { question1: "answer1", question2: "answer2" },
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prismaWriteService.prisma.app_RoutingForms_FormResponse.deleteMany({
|
||||
where: {
|
||||
formId: routingForm.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.app_RoutingForms_Form.deleteMany({
|
||||
where: {
|
||||
teamId: org.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.apiKey.deleteMany({
|
||||
where: {
|
||||
teamId: org.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: org.id,
|
||||
},
|
||||
});
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe(`GET /v2/organizations/:orgId/routing-forms/:routingFormId/responses`, () => {
|
||||
it("should not get routing form responses for non existing org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/99999/routing-forms/${routingForm.id}/responses`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should not get routing form responses for non existing form", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/routing-forms/non-existent-id/responses`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not get routing form responses without authentication", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/routing-forms/${routingForm.id}/responses`)
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it("should get routing form responses", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/organizations/${org.id}/routing-forms/${routingForm.id}/responses?skip=0&take=2&sortUpdatedAt=asc&sortCreatedAt=desc`
|
||||
)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const responses = responseBody.data as App_RoutingForms_FormResponse[];
|
||||
expect(responses).toBeDefined();
|
||||
expect(responses.length).toBeGreaterThan(0);
|
||||
expect(responses.find((response) => response.id === routingFormResponse.id)).toBeDefined();
|
||||
expect(responses.find((response) => response.id === routingFormResponse.id)?.formId).toEqual(
|
||||
routingFormResponse.formId
|
||||
);
|
||||
expect(responses.find((response) => response.id === routingFormResponse2.id)).toBeDefined();
|
||||
expect(responses.find((response) => response.id === routingFormResponse2.id)?.formId).toEqual(
|
||||
routingFormResponse2.formId
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`PATCH /v2/organizations/:orgId/routing-forms/:routingFormId/responses/:responseId`, () => {
|
||||
it("should not update routing form response for non existing org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/99999/routing-forms/${routingForm.id}/responses/${routingFormResponse.id}`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should not update routing form response for non existing form", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(
|
||||
`/v2/organizations/${org.id}/routing-forms/non-existent-id/responses/${routingFormResponse.id}`
|
||||
)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not update routing form response for non existing response", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/routing-forms/${routingForm.id}/responses/99999`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not update routing form response without authentication", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(
|
||||
`/v2/organizations/${org.id}/routing-forms/${routingForm.id}/responses/${routingFormResponse.id}`
|
||||
)
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it("should update routing form response", async () => {
|
||||
const updatedResponse = { question1: "updated_answer1", question2: "updated_answer2" };
|
||||
return request(app.getHttpServer())
|
||||
.patch(
|
||||
`/v2/organizations/${org.id}/routing-forms/${routingForm.id}/responses/${routingFormResponse.id}`
|
||||
)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: updatedResponse })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data;
|
||||
expect(data).toBeDefined();
|
||||
expect(data.id).toEqual(routingFormResponse.id);
|
||||
expect(data.formId).toEqual(routingFormResponse.formId);
|
||||
expect(data.response).toEqual(updatedResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
|
||||
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { GetRoutingFormResponsesOutput } from "@/modules/organizations/routing-forms/outputs/get-routing-form-responses.output";
|
||||
import { OrganizationsRoutingFormsResponsesService } from "@/modules/organizations/routing-forms/services/organizations-routing-forms-responses.service";
|
||||
import { Body, Controller, Get, Param, Patch, Query, UseGuards, ParseIntPipe } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
import { GetRoutingFormResponsesParams } from "../inputs/get-routing-form-responses-params.input";
|
||||
import { UpdateRoutingFormResponseInput } from "../inputs/update-routing-form-response.input";
|
||||
import { UpdateRoutingFormResponseOutput } from "../outputs/update-routing-form-response.output";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId/routing-forms/:routingFormId/responses",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||
@ApiTags("Orgs / Routing forms")
|
||||
@ApiHeader(API_KEY_HEADER)
|
||||
export class OrganizationsRoutingFormsResponsesController {
|
||||
constructor(
|
||||
private readonly organizationsRoutingFormsResponsesService: OrganizationsRoutingFormsResponsesService
|
||||
) {}
|
||||
|
||||
@Get("/")
|
||||
@ApiOperation({ summary: "Get routing form responses" })
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async getRoutingFormResponses(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("routingFormId") routingFormId: string,
|
||||
@Query() queryParams: GetRoutingFormResponsesParams
|
||||
): Promise<GetRoutingFormResponsesOutput> {
|
||||
const { skip, take, ...filters } = queryParams;
|
||||
|
||||
const responses =
|
||||
await this.organizationsRoutingFormsResponsesService.getOrganizationRoutingFormResponses(
|
||||
orgId,
|
||||
routingFormId,
|
||||
skip ?? 0,
|
||||
take ?? 250,
|
||||
filters
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: responses,
|
||||
};
|
||||
}
|
||||
|
||||
@Patch("/:responseId")
|
||||
@ApiOperation({ summary: "Update routing form response" })
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async updateRoutingFormResponse(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("routingFormId") routingFormId: string,
|
||||
@Param("responseId", ParseIntPipe) responseId: number,
|
||||
@Body() updateRoutingFormResponseInput: UpdateRoutingFormResponseInput
|
||||
): Promise<UpdateRoutingFormResponseOutput> {
|
||||
const updatedResponse = await this.organizationsRoutingFormsResponsesService.updateRoutingFormResponse(
|
||||
orgId,
|
||||
routingFormId,
|
||||
responseId,
|
||||
updateRoutingFormResponseInput
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: updatedResponse,
|
||||
};
|
||||
}
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { GetRoutingFormsOutput } from "@/modules/organizations/routing-forms/outputs/get-routing-forms.output";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { App_RoutingForms_Form, App_RoutingForms_FormResponse, Team, User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
describe("OrganizationsRoutingFormController", () => {
|
||||
let app: INestApplication;
|
||||
let prismaWriteService: PrismaWriteService;
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
let apiKeyString: string;
|
||||
let routingForm: App_RoutingForms_Form;
|
||||
let routingFormResponse: App_RoutingForms_FormResponse;
|
||||
let routingFormResponse2: App_RoutingForms_FormResponse;
|
||||
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
|
||||
let user: User;
|
||||
const userEmail = `OrganizationsRoutingFormsResponsesController-key-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
}).compile();
|
||||
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
|
||||
prismaWriteService = moduleRef.get<PrismaWriteService>(PrismaWriteService);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `OrganizationsRoutingFormsResponsesController-teams-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "OrganizationsRoutingFormsResponsesController orgs booking 1",
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${user.id}`,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = `${keyString}`;
|
||||
|
||||
routingForm = await prismaWriteService.prisma.app_RoutingForms_Form.create({
|
||||
data: {
|
||||
name: "Test Routing Form",
|
||||
description: "Test Description",
|
||||
disabled: false,
|
||||
routes: [{ redirect: "http://google.com" }],
|
||||
fields: [{ territory: "input" }],
|
||||
settings: { test: "true" },
|
||||
teamId: team.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
routingFormResponse = await prismaWriteService.prisma.app_RoutingForms_FormResponse.create({
|
||||
data: {
|
||||
formId: routingForm.id,
|
||||
response: JSON.stringify({ question1: "answer1", question2: "answer2" }),
|
||||
},
|
||||
});
|
||||
|
||||
routingFormResponse2 = await prismaWriteService.prisma.app_RoutingForms_FormResponse.create({
|
||||
data: {
|
||||
formId: routingForm.id,
|
||||
response: JSON.stringify({ question1: "answer1", question2: "answer2" }),
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prismaWriteService.prisma.app_RoutingForms_Form.deleteMany({
|
||||
where: {
|
||||
teamId: org.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.apiKey.deleteMany({
|
||||
where: {
|
||||
teamId: org.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: org.id,
|
||||
},
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe(`GET /v2/organizations/:orgId/routing-forms`, () => {
|
||||
it("should not get routing forms for non existing org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/99999/routing-forms`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should not get routing forms without authentication", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/routing-forms`).expect(401);
|
||||
});
|
||||
|
||||
it("should get organization routing forms", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/routing-forms?skip=0&take=1`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].id).toEqual(routingForm.id);
|
||||
expect(routingForms[0].name).toEqual(routingForm.name);
|
||||
expect(routingForms[0].description).toEqual(routingForm.description);
|
||||
expect(routingForms[0].disabled).toEqual(routingForm.disabled);
|
||||
});
|
||||
});
|
||||
|
||||
it("should filter routing forms by name", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/routing-forms?name=Test`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].name).toContain("Test");
|
||||
});
|
||||
});
|
||||
|
||||
it("should filter routing forms by disabled status", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/routing-forms?disabled=false`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].disabled).toEqual(false);
|
||||
expect(routingForms[0].fields?.[0]).toEqual({ territory: "input" });
|
||||
expect(routingForms[0].routes?.[0]).toEqual({ redirect: "http://google.com" });
|
||||
expect(routingForms[0].settings).toEqual({ test: "true" });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
|
||||
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { GetRoutingFormsParams } from "@/modules/organizations/routing-forms/inputs/get-routing-form-responses-params.input";
|
||||
import {
|
||||
GetRoutingFormsOutput,
|
||||
RoutingFormOutput,
|
||||
} from "@/modules/organizations/routing-forms/outputs/get-routing-forms.output";
|
||||
import { OrganizationsRoutingFormsService } from "@/modules/organizations/routing-forms/services/organizations-routing-forms.service";
|
||||
import { Controller, Get, Param, Query, UseGuards, ParseIntPipe } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId/routing-forms",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||
@ApiTags("Orgs / Routing forms")
|
||||
@ApiHeader(API_KEY_HEADER)
|
||||
export class OrganizationsRoutingFormsController {
|
||||
constructor(private readonly organizationsRoutingFormsService: OrganizationsRoutingFormsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get organization routing forms" })
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async getOrganizationRoutingForms(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Query() queryParams: GetRoutingFormsParams
|
||||
): Promise<GetRoutingFormsOutput> {
|
||||
const { skip, take, ...filters } = queryParams;
|
||||
|
||||
const routingForms = await this.organizationsRoutingFormsService.getOrganizationRoutingForms(
|
||||
orgId,
|
||||
skip ?? 0,
|
||||
take ?? 250,
|
||||
filters
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: routingForms.map((form) => plainToClass(RoutingFormOutput, form)),
|
||||
};
|
||||
}
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import {
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsEnum,
|
||||
IsISO8601,
|
||||
IsDate,
|
||||
IsNumber,
|
||||
IsArray,
|
||||
ArrayMinSize,
|
||||
} from "class-validator";
|
||||
|
||||
enum SortOrder {
|
||||
ASC = "asc",
|
||||
DESC = "desc",
|
||||
}
|
||||
|
||||
export class GetRoutingFormResponsesParams {
|
||||
@ApiPropertyOptional({ type: Number, description: "Number of responses to skip" })
|
||||
@Transform(({ value }) => value && parseInt(value))
|
||||
@IsOptional()
|
||||
skip?: number;
|
||||
|
||||
@ApiPropertyOptional({ type: Number, description: "Number of responses to take" })
|
||||
@Transform(({ value }) => value && parseInt(value))
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@ApiPropertyOptional({ enum: SortOrder, description: "Sort by creation time" })
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder)
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
|
||||
@ApiPropertyOptional({ enum: SortOrder, description: "Sort by update time" })
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder)
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by responses created after this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
afterCreatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by responses created before this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
beforeCreatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by responses created after this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
afterUpdatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by responses updated before this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
beforeUpdatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({ type: String, description: "Filter by responses routed to a specific booking" })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
routedToBookingUid?: string;
|
||||
}
|
||||
|
||||
export class GetRoutingFormsParams extends GetRoutingFormResponsesParams {
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === "string") {
|
||||
return value.split(",").map((teamId: string) => parseInt(teamId));
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@ApiPropertyOptional({
|
||||
type: [Number],
|
||||
description: "Filter by teamIds. Team ids must be separated by a comma.",
|
||||
example: "?teamIds=100,200",
|
||||
})
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
@ArrayMinSize(1, { message: "teamIds must contain at least 1 team id" })
|
||||
teamIds?: number[];
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsOptional, IsBoolean, IsString, IsEnum, IsDate, IsISO8601 } from "class-validator";
|
||||
|
||||
enum SortOrder {
|
||||
ASC = "asc",
|
||||
DESC = "desc",
|
||||
}
|
||||
|
||||
export class GetRoutingFormsParams {
|
||||
@ApiPropertyOptional({ type: Number, description: "Number of routing forms to skip" })
|
||||
@Transform(({ value }) => value && parseInt(value))
|
||||
@IsOptional()
|
||||
skip?: number;
|
||||
|
||||
@ApiPropertyOptional({ type: Number, description: "Number of routing forms to take" })
|
||||
@Transform(({ value }) => value && parseInt(value))
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@ApiPropertyOptional({ enum: SortOrder, description: "Sort by creation time" })
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder)
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
|
||||
@ApiPropertyOptional({ enum: SortOrder, description: "Sort by update time" })
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder)
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
|
||||
@ApiPropertyOptional({ type: Boolean, description: "Filter by disabled status" })
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (value === "true") return true;
|
||||
if (value === "false") return false;
|
||||
return value;
|
||||
})
|
||||
@IsBoolean()
|
||||
disabled?: boolean;
|
||||
|
||||
@ApiPropertyOptional({ type: String, description: "Filter by name" })
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by forms created after this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
afterCreatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by forms created before this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
beforeCreatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by forms updated after this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
afterUpdatedAt?: Date;
|
||||
|
||||
@ApiPropertyOptional({
|
||||
type: String,
|
||||
format: "date-time",
|
||||
description: "Filter by forms updated before this date",
|
||||
})
|
||||
@IsOptional()
|
||||
@IsISO8601()
|
||||
@Transform(({ value }) => value && new Date(value))
|
||||
@IsDate()
|
||||
beforeUpdatedAt?: Date;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsOptional } from "class-validator";
|
||||
|
||||
export class UpdateRoutingFormResponseInput {
|
||||
@ApiPropertyOptional({ type: Object, description: "The updated response data" })
|
||||
@IsOptional()
|
||||
response?: Record<string, any>;
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesOutputService } from "@/modules/organizations/teams/routing-forms/services/organizations-teams-routing-forms-responses-output.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { RoutingFormsModule } from "@/modules/routing-forms/routing-forms.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsRoutingFormsResponsesController } from "./controllers/organizations-routing-forms-responses.controller";
|
||||
import { OrganizationsRoutingFormsController } from "./controllers/organizations-routing-forms.controller";
|
||||
import { OrganizationsRoutingFormsRepository } from "./organizations-routing-forms.repository";
|
||||
import { OrganizationsRoutingFormsResponsesService } from "./services/organizations-routing-forms-responses.service";
|
||||
import { OrganizationsRoutingFormsService } from "./services/organizations-routing-forms.service";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule, RedisModule, RoutingFormsModule],
|
||||
providers: [
|
||||
MembershipsRepository,
|
||||
OrganizationsRepository,
|
||||
OrganizationsRoutingFormsRepository,
|
||||
OrganizationsRoutingFormsService,
|
||||
OrganizationsRoutingFormsResponsesService,
|
||||
OrganizationsTeamsRoutingFormsResponsesOutputService,
|
||||
],
|
||||
controllers: [OrganizationsRoutingFormsController, OrganizationsRoutingFormsResponsesController],
|
||||
})
|
||||
export class OrganizationsRoutingFormsModule {}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsRoutingFormsRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getOrganizationRoutingForms(
|
||||
orgId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
teamIds?: number[];
|
||||
}
|
||||
) {
|
||||
const {
|
||||
disabled,
|
||||
name,
|
||||
sortCreatedAt,
|
||||
sortUpdatedAt,
|
||||
afterCreatedAt,
|
||||
beforeCreatedAt,
|
||||
afterUpdatedAt,
|
||||
beforeUpdatedAt,
|
||||
teamIds,
|
||||
} = options || {};
|
||||
|
||||
return this.dbRead.prisma.app_RoutingForms_Form.findMany({
|
||||
where: {
|
||||
team: { parentId: orgId, ...(teamIds?.length ? { id: { in: teamIds } } : {}) },
|
||||
...(disabled !== undefined && { disabled }),
|
||||
...(name && { name: { contains: name, mode: "insensitive" } }),
|
||||
...(afterCreatedAt && { createdAt: { gte: afterCreatedAt } }),
|
||||
...(beforeCreatedAt && { createdAt: { lte: beforeCreatedAt } }),
|
||||
...(afterUpdatedAt && { updatedAt: { gte: afterUpdatedAt } }),
|
||||
...(beforeUpdatedAt && { updatedAt: { lte: beforeUpdatedAt } }),
|
||||
},
|
||||
orderBy: [
|
||||
...(sortCreatedAt ? [{ createdAt: sortCreatedAt }] : []),
|
||||
...(sortUpdatedAt ? [{ updatedAt: sortUpdatedAt }] : []),
|
||||
],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async getOrganizationRoutingFormResponses(
|
||||
orgId: number,
|
||||
routingFormId: string,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
routedToBookingUid?: string;
|
||||
}
|
||||
) {
|
||||
const {
|
||||
sortCreatedAt,
|
||||
sortUpdatedAt,
|
||||
afterCreatedAt,
|
||||
beforeCreatedAt,
|
||||
routedToBookingUid,
|
||||
afterUpdatedAt,
|
||||
beforeUpdatedAt,
|
||||
} = options || {};
|
||||
await this.dbRead.prisma.app_RoutingForms_Form.findFirstOrThrow({
|
||||
where: {
|
||||
team: { parentId: orgId },
|
||||
id: routingFormId,
|
||||
},
|
||||
});
|
||||
|
||||
return this.dbRead.prisma.app_RoutingForms_FormResponse.findMany({
|
||||
where: {
|
||||
formId: routingFormId,
|
||||
...(afterCreatedAt && { createdAt: { gte: afterCreatedAt } }),
|
||||
...(beforeCreatedAt && { createdAt: { lte: beforeCreatedAt } }),
|
||||
...(afterUpdatedAt && { updatedAt: { gte: afterUpdatedAt } }),
|
||||
...(beforeUpdatedAt && { updatedAt: { lte: beforeUpdatedAt } }),
|
||||
...(routedToBookingUid && { routedToBookingUid }),
|
||||
},
|
||||
orderBy: [
|
||||
...(sortCreatedAt ? [{ createdAt: sortCreatedAt }] : []),
|
||||
...(sortUpdatedAt ? [{ updatedAt: sortUpdatedAt }] : []),
|
||||
],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async updateRoutingFormResponse(
|
||||
orgId: number,
|
||||
routingFormId: string,
|
||||
responseId: number,
|
||||
data: {
|
||||
response?: Record<string, any>;
|
||||
}
|
||||
) {
|
||||
return this.dbWrite.prisma.app_RoutingForms_FormResponse.update({
|
||||
where: {
|
||||
id: responseId,
|
||||
formId: routingFormId,
|
||||
form: {
|
||||
team: {
|
||||
parentId: orgId,
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { RoutingFormResponseOutput } from "@calcom/platform-types";
|
||||
|
||||
export class GetRoutingFormResponsesOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({ type: RoutingFormResponseOutput })
|
||||
@Expose()
|
||||
@Type(() => RoutingFormResponseOutput)
|
||||
data!: RoutingFormResponseOutput[];
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class RoutingFormOutput {
|
||||
@Expose()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty({ example: "My Form" })
|
||||
@Expose()
|
||||
name!: string;
|
||||
|
||||
@ApiProperty({ example: "This is the description." })
|
||||
@Expose()
|
||||
description!: string | null;
|
||||
|
||||
@ApiProperty({ example: 0 })
|
||||
@Expose()
|
||||
position!: number;
|
||||
|
||||
@Expose()
|
||||
routes!: Record<string, unknown> | null;
|
||||
|
||||
@ApiProperty({ example: "2024-03-28T10:00:00.000Z" })
|
||||
@Expose()
|
||||
createdAt!: string;
|
||||
|
||||
@ApiProperty({ example: "2024-03-28T10:00:00.000Z" })
|
||||
@Expose()
|
||||
updatedAt!: string;
|
||||
|
||||
@Expose()
|
||||
fields!: Record<string, unknown> | null;
|
||||
|
||||
@ApiProperty({ example: 2313 })
|
||||
@Expose()
|
||||
userId!: number;
|
||||
|
||||
@ApiProperty({ example: 4214321 })
|
||||
@Expose()
|
||||
teamId!: number | null;
|
||||
|
||||
@ApiProperty({ example: false })
|
||||
@Expose()
|
||||
disabled!: boolean;
|
||||
|
||||
@Expose()
|
||||
settings!: Record<string, unknown> | null;
|
||||
}
|
||||
|
||||
export class GetRoutingFormsOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({ type: [RoutingFormOutput] })
|
||||
@Expose()
|
||||
@Type(() => RoutingFormOutput)
|
||||
data!: RoutingFormOutput[];
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsEnum } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
import { RoutingFormResponseOutput } from "@calcom/platform-types";
|
||||
|
||||
export class UpdateRoutingFormResponseOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({ type: RoutingFormResponseOutput })
|
||||
@Expose()
|
||||
@Type(() => RoutingFormResponseOutput)
|
||||
data!: RoutingFormResponseOutput;
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { OrganizationsRoutingFormsRepository } from "@/modules/organizations/routing-forms/organizations-routing-forms.repository";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesOutputService } from "@/modules/organizations/teams/routing-forms/services/organizations-teams-routing-forms-responses-output.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsRoutingFormsResponsesService {
|
||||
constructor(
|
||||
private readonly organizationsRoutingFormsRepository: OrganizationsRoutingFormsRepository,
|
||||
private readonly outputService: OrganizationsTeamsRoutingFormsResponsesOutputService
|
||||
) {}
|
||||
|
||||
async getOrganizationRoutingFormResponses(
|
||||
orgId: number,
|
||||
routingFormId: string,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
routedToBookingUid?: string;
|
||||
}
|
||||
) {
|
||||
const responses = await this.organizationsRoutingFormsRepository.getOrganizationRoutingFormResponses(
|
||||
orgId,
|
||||
routingFormId,
|
||||
skip,
|
||||
take,
|
||||
options
|
||||
);
|
||||
|
||||
return this.outputService.getRoutingFormResponses(responses);
|
||||
}
|
||||
|
||||
async updateRoutingFormResponse(
|
||||
orgId: number,
|
||||
routingFormId: string,
|
||||
responseId: number,
|
||||
data: {
|
||||
response?: Record<string, any>;
|
||||
}
|
||||
) {
|
||||
const updatedResponse = await this.organizationsRoutingFormsRepository.updateRoutingFormResponse(
|
||||
orgId,
|
||||
routingFormId,
|
||||
responseId,
|
||||
data
|
||||
);
|
||||
|
||||
return this.outputService.getRoutingFormResponses([updatedResponse])[0];
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { OrganizationsRoutingFormsRepository } from "@/modules/organizations/routing-forms/organizations-routing-forms.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsRoutingFormsService {
|
||||
constructor(private readonly organizationsRoutingFormsRepository: OrganizationsRoutingFormsRepository) {}
|
||||
|
||||
async getOrganizationRoutingForms(
|
||||
orgId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
teamIds?: number[];
|
||||
}
|
||||
) {
|
||||
return this.organizationsRoutingFormsRepository.getOrganizationRoutingForms(orgId, skip, take, options);
|
||||
}
|
||||
}
|
||||
+60
-2
@@ -35,7 +35,7 @@ describe("Organizations Teams Routing Forms Responses", () => {
|
||||
|
||||
let org: Team;
|
||||
let orgTeam: Team;
|
||||
|
||||
let routingFormResponseId: string;
|
||||
const authEmail = `organizations-teams-routing-forms-responses-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let apiKeyString: string;
|
||||
@@ -173,7 +173,9 @@ describe("Organizations Teams Routing Forms Responses", () => {
|
||||
|
||||
it("should get routing form responses", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/${orgTeam.id}/routing-forms/${routingFormId}/responses`)
|
||||
.get(
|
||||
`/v2/organizations/${org.id}/teams/${orgTeam.id}/routing-forms/${routingFormId}/responses?skip=0&take=1`
|
||||
)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
@@ -186,9 +188,65 @@ describe("Organizations Teams Routing Forms Responses", () => {
|
||||
expect(responseData[0].response).toEqual(routingFormResponses[0].response);
|
||||
expect(responseData[0].formFillerId).toEqual(routingFormResponses[0].formFillerId);
|
||||
expect(responseData[0].createdAt).toEqual(routingFormResponses[0].createdAt.toISOString());
|
||||
routingFormResponseId = responseData[0].id;
|
||||
});
|
||||
});
|
||||
|
||||
describe(`PATCH /v2/organizations/:orgId/routing-forms/:routingFormId/responses/:responseId`, () => {
|
||||
it("should not update routing form response for non existing org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/99999/routing-forms/${routingFormId}/responses/${routingFormResponseId}`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should not update routing form response for non existing form", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/routing-forms/non-existent-id/responses/${routingFormResponseId}`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not update routing form response for non existing response", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/routing-forms/${routingFormId}/responses/99999`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not update routing form response without authentication", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(
|
||||
`/v2/organizations/${org.id}/routing-forms/${routingFormId}/responses/${routingFormResponseId}`
|
||||
)
|
||||
.send({ response: JSON.stringify({ question1: "updated_answer1" }) })
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it("should update routing form response", async () => {
|
||||
const updatedResponse = { question1: "updated_answer1", question2: "updated_answer2" };
|
||||
return request(app.getHttpServer())
|
||||
.patch(
|
||||
`/v2/organizations/${org.id}/routing-forms/${routingFormId}/responses/${routingFormResponseId}`
|
||||
)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.send({ response: updatedResponse })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data;
|
||||
expect(data).toBeDefined();
|
||||
expect(data.id).toEqual(routingFormResponseId);
|
||||
expect(data.formId).toEqual(routingFormId);
|
||||
expect(data.response).toEqual(updatedResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await organizationsRepositoryFixture.delete(org.id);
|
||||
|
||||
+50
-12
@@ -8,14 +8,16 @@ import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-a
|
||||
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { IsRoutingFormInTeam } from "@/modules/auth/guards/routing-forms/is-routing-form-in-team.guard";
|
||||
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||
import { Controller, Get, Param, UseGuards } from "@nestjs/common";
|
||||
import { GetRoutingFormResponsesParams } from "@/modules/organizations/routing-forms/inputs/get-routing-form-responses-params.input";
|
||||
import { UpdateRoutingFormResponseInput } from "@/modules/organizations/routing-forms/inputs/update-routing-form-response.input";
|
||||
import { UpdateRoutingFormResponseOutput } from "@/modules/organizations/routing-forms/outputs/update-routing-form-response.output";
|
||||
import { GetRoutingFormResponsesOutput } from "@/modules/organizations/teams/routing-forms/outputs/get-routing-form-responses.output";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesService } from "@/modules/organizations/teams/routing-forms/services/organizations-teams-routing-forms-responses.service";
|
||||
import { Controller, Get, Patch, Param, ParseIntPipe, Query, UseGuards, Body } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
import { RoutingFormsResponsesService } from "../../../../routing-forms-responses/services/routing-forms-responses.service";
|
||||
import { GetRoutingFormResponsesOutput } from "../outputs/get-routing-form-responses.output";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId/teams/:teamId/routing-forms/:routingFormId/responses",
|
||||
version: API_VERSIONS_VALUES,
|
||||
@@ -31,22 +33,58 @@ import { GetRoutingFormResponsesOutput } from "../outputs/get-routing-form-respo
|
||||
)
|
||||
@ApiHeader(API_KEY_HEADER)
|
||||
export class OrganizationsTeamsRoutingFormsResponsesController {
|
||||
constructor(private readonly routingFormsResponsesService: RoutingFormsResponsesService) {}
|
||||
constructor(
|
||||
private readonly organizationsTeamsRoutingFormsResponsesService: OrganizationsTeamsRoutingFormsResponsesService
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get routing form responses" })
|
||||
@Roles("ORG_ADMIN")
|
||||
@Get("/")
|
||||
@ApiOperation({ summary: "Get organization team routing form responses" })
|
||||
@Roles("TEAM_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async getRoutingFormResponses(
|
||||
@Param("routingFormId") routingFormId: string
|
||||
@Param("routingFormId") routingFormId: string,
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Query() queryParams: GetRoutingFormResponsesParams
|
||||
): Promise<GetRoutingFormResponsesOutput> {
|
||||
const routingFormResponses = await this.routingFormsResponsesService.getRoutingFormResponses(
|
||||
routingFormId
|
||||
);
|
||||
const { skip, take, ...filters } = queryParams;
|
||||
|
||||
const routingFormResponses =
|
||||
await this.organizationsTeamsRoutingFormsResponsesService.getTeamRoutingFormResponses(
|
||||
teamId,
|
||||
routingFormId,
|
||||
skip ?? 0,
|
||||
take ?? 250,
|
||||
{ ...(filters ?? {}) }
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: routingFormResponses,
|
||||
};
|
||||
}
|
||||
|
||||
@Patch("/:responseId")
|
||||
@ApiOperation({ summary: "Update routing form response" })
|
||||
@Roles("TEAM_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async updateRoutingFormResponse(
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Param("routingFormId") routingFormId: string,
|
||||
@Param("responseId", ParseIntPipe) responseId: number,
|
||||
@Body() updateRoutingFormResponseInput: UpdateRoutingFormResponseInput
|
||||
): Promise<UpdateRoutingFormResponseOutput> {
|
||||
const updatedResponse =
|
||||
await this.organizationsTeamsRoutingFormsResponsesService.updateTeamRoutingFormResponse(
|
||||
teamId,
|
||||
routingFormId,
|
||||
responseId,
|
||||
updateRoutingFormResponseInput
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: updatedResponse,
|
||||
};
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { GetRoutingFormsOutput } from "@/modules/organizations/routing-forms/outputs/get-routing-forms.output";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { App_RoutingForms_Form, Team, User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
describe("OrganizationsRoutingFormsResponsesController", () => {
|
||||
let app: INestApplication;
|
||||
let prismaWriteService: PrismaWriteService;
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
let apiKeyString: string;
|
||||
let routingForm: App_RoutingForms_Form;
|
||||
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
|
||||
let user: User;
|
||||
const userEmail = `OrganizationsRoutingFormsResponsesController-key-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
}).compile();
|
||||
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
|
||||
prismaWriteService = moduleRef.get<PrismaWriteService>(PrismaWriteService);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `OrganizationsRoutingFormsResponsesController-teams-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "OrganizationsRoutingFormsResponsesController orgs booking 1",
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${user.id}`,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
const now = new Date();
|
||||
now.setDate(now.getDate() + 1);
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = `${keyString}`;
|
||||
|
||||
routingForm = await prismaWriteService.prisma.app_RoutingForms_Form.create({
|
||||
data: {
|
||||
name: "Test Routing Form",
|
||||
description: "Test Description",
|
||||
disabled: false,
|
||||
routes: JSON.stringify([]),
|
||||
fields: JSON.stringify([]),
|
||||
settings: JSON.stringify({}),
|
||||
teamId: team.id,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await prismaWriteService.prisma.app_RoutingForms_Form.deleteMany({
|
||||
where: {
|
||||
teamId: team.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.apiKey.deleteMany({
|
||||
where: {
|
||||
teamId: org.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: team.id,
|
||||
},
|
||||
});
|
||||
await prismaWriteService.prisma.team.delete({
|
||||
where: {
|
||||
id: org.id,
|
||||
},
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
|
||||
describe(`GET /v2/organizations/:orgId/teams/:teamId/routing-forms`, () => {
|
||||
it("should not get team routing forms for non existing org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/99999/teams/${team.id}/routing-forms`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should not get team routing forms for non existing team", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/99999/routing-forms`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it("should not get team routing forms without authentication", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/${team.id}/routing-forms`)
|
||||
.expect(401);
|
||||
});
|
||||
|
||||
it("should get team routing forms", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/${team.id}/routing-forms`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].id).toEqual(routingForm.id);
|
||||
expect(routingForms[0].name).toEqual(routingForm.name);
|
||||
expect(routingForms[0].description).toEqual(routingForm.description);
|
||||
expect(routingForms[0].disabled).toEqual(routingForm.disabled);
|
||||
});
|
||||
});
|
||||
|
||||
it("should filter team routing forms by name", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/${team.id}/routing-forms?name=Team`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].name).toContain("Test Routing Form");
|
||||
});
|
||||
});
|
||||
|
||||
it("should filter team routing forms by disabled status", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/teams/${team.id}/routing-forms?disabled=false`)
|
||||
.set({ Authorization: `Bearer cal_test_${apiKeyString}` })
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody: GetRoutingFormsOutput = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const routingForms = responseBody.data;
|
||||
expect(routingForms).toBeDefined();
|
||||
expect(routingForms.length).toBeGreaterThan(0);
|
||||
expect(routingForms[0].disabled).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||
import { IsAdminAPIEnabledGuard } from "@/modules/auth/guards/organizations/is-admin-api-enabled.guard";
|
||||
import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||
import { GetRoutingFormResponsesParams } from "@/modules/organizations/routing-forms/inputs/get-routing-form-responses-params.input";
|
||||
import {
|
||||
GetRoutingFormsOutput,
|
||||
RoutingFormOutput,
|
||||
} from "@/modules/organizations/routing-forms/outputs/get-routing-forms.output";
|
||||
import { OrganizationsTeamsRoutingFormsService } from "@/modules/organizations/teams/routing-forms/services/organizations-teams-routing-forms.service";
|
||||
import { Controller, Get, Param, Query, UseGuards, ParseIntPipe } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId/teams/:teamId/routing-forms",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, IsTeamInOrg, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||
@ApiTags("Orgs / Teams / Routing forms")
|
||||
@ApiHeader(API_KEY_HEADER)
|
||||
export class OrganizationsTeamsRoutingFormsController {
|
||||
constructor(
|
||||
private readonly organizationsTeamsRoutingFormsService: OrganizationsTeamsRoutingFormsService
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get team routing forms" })
|
||||
@Roles("TEAM_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
async getTeamRoutingForms(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Query() queryParams: GetRoutingFormResponsesParams
|
||||
): Promise<GetRoutingFormsOutput> {
|
||||
const { skip, take, ...filters } = queryParams;
|
||||
|
||||
const routingForms = await this.organizationsTeamsRoutingFormsService.getTeamRoutingForms(
|
||||
teamId,
|
||||
skip ?? 0,
|
||||
take ?? 250,
|
||||
{ ...(filters ?? {}) }
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: routingForms.map((form) => plainToClass(RoutingFormOutput, form)),
|
||||
};
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { RoutingFormsResponsesModule } from "@/modules/routing-forms-responses/routing-forms-responses.module";
|
||||
import { RoutingFormsModule } from "@/modules/routing-forms/routing-forms.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsTeamsRoutingFormsResponsesController } from "./controllers/organizations-teams-routing-forms-responses.controller";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule, RedisModule, RoutingFormsResponsesModule, RoutingFormsModule],
|
||||
providers: [OrganizationsRepository, OrganizationsTeamsRepository],
|
||||
controllers: [OrganizationsTeamsRoutingFormsResponsesController],
|
||||
})
|
||||
export class OrganizationsTeamsRoutingFormsModule {}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { OrganizationsRoutingFormsRepository } from "@/modules/organizations/routing-forms/organizations-routing-forms.repository";
|
||||
import { OrganizationsRoutingFormsResponsesService } from "@/modules/organizations/routing-forms/services/organizations-routing-forms-responses.service";
|
||||
import { OrganizationsTeamsRepository } from "@/modules/organizations/teams/index/organizations-teams.repository";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RedisModule } from "@/modules/redis/redis.module";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { RoutingFormsModule } from "@/modules/routing-forms/routing-forms.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsTeamsRoutingFormsResponsesController } from "./controllers/organizations-teams-routing-forms-responses.controller";
|
||||
import { OrganizationsTeamsRoutingFormsController } from "./controllers/organizations-teams-routing-forms.controller";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesRepository } from "./repositories/organizations-teams-routing-forms-responses.repository";
|
||||
import { OrganizationsTeamsRoutingFormsRepository } from "./repositories/organizations-teams-routing-forms.repository";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesOutputService } from "./services/organizations-teams-routing-forms-responses-output.service";
|
||||
import { OrganizationsTeamsRoutingFormsResponsesService } from "./services/organizations-teams-routing-forms-responses.service";
|
||||
import { OrganizationsTeamsRoutingFormsService } from "./services/organizations-teams-routing-forms.service";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, StripeModule, RedisModule, RoutingFormsModule],
|
||||
providers: [
|
||||
OrganizationsTeamsRoutingFormsService,
|
||||
OrganizationsTeamsRoutingFormsResponsesService,
|
||||
OrganizationsTeamsRoutingFormsResponsesOutputService,
|
||||
OrganizationsTeamsRoutingFormsResponsesRepository,
|
||||
OrganizationsTeamsRoutingFormsRepository,
|
||||
OrganizationsRepository,
|
||||
OrganizationsTeamsRepository,
|
||||
MembershipsRepository,
|
||||
OrganizationsRoutingFormsResponsesService,
|
||||
OrganizationsRoutingFormsRepository,
|
||||
],
|
||||
controllers: [OrganizationsTeamsRoutingFormsResponsesController, OrganizationsTeamsRoutingFormsController],
|
||||
})
|
||||
export class OrganizationsTeamsRoutingFormsModule {}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsRoutingFormsResponsesRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getTeamRoutingFormResponses(
|
||||
teamId: number,
|
||||
routingFormId: string,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
routedToBookingUid?: string;
|
||||
}
|
||||
) {
|
||||
const { sortCreatedAt, afterCreatedAt, beforeCreatedAt, routedToBookingUid } = options || {};
|
||||
|
||||
return this.dbRead.prisma.app_RoutingForms_FormResponse.findMany({
|
||||
where: {
|
||||
formId: routingFormId,
|
||||
form: {
|
||||
teamId,
|
||||
},
|
||||
...(afterCreatedAt && { createdAt: { gte: afterCreatedAt } }),
|
||||
...(beforeCreatedAt && { createdAt: { lte: beforeCreatedAt } }),
|
||||
...(routedToBookingUid && { routedToBookingUid }),
|
||||
},
|
||||
orderBy: [...(sortCreatedAt ? [{ createdAt: sortCreatedAt }] : [])],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
|
||||
async updateTeamRoutingFormResponse(
|
||||
teamId: number,
|
||||
routingFormId: string,
|
||||
responseId: number,
|
||||
data: {
|
||||
response?: Record<string, any>;
|
||||
}
|
||||
) {
|
||||
return this.dbWrite.prisma.app_RoutingForms_FormResponse.update({
|
||||
where: {
|
||||
id: responseId,
|
||||
formId: routingFormId,
|
||||
form: {
|
||||
teamId,
|
||||
},
|
||||
},
|
||||
data: {
|
||||
...data,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsRoutingFormsRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getTeamRoutingForms(
|
||||
teamId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
}
|
||||
) {
|
||||
const {
|
||||
disabled,
|
||||
name,
|
||||
sortCreatedAt,
|
||||
sortUpdatedAt,
|
||||
afterCreatedAt,
|
||||
beforeCreatedAt,
|
||||
afterUpdatedAt,
|
||||
beforeUpdatedAt,
|
||||
} = options || {};
|
||||
|
||||
return this.dbRead.prisma.app_RoutingForms_Form.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
...(disabled !== undefined && { disabled }),
|
||||
...(name && { name: { contains: name, mode: "insensitive" } }),
|
||||
...(afterCreatedAt && { createdAt: { gte: afterCreatedAt } }),
|
||||
...(beforeCreatedAt && { createdAt: { lte: beforeCreatedAt } }),
|
||||
...(afterUpdatedAt && { updatedAt: { gte: afterUpdatedAt } }),
|
||||
...(beforeUpdatedAt && { updatedAt: { lte: beforeUpdatedAt } }),
|
||||
},
|
||||
orderBy: [
|
||||
...(sortCreatedAt ? [{ createdAt: sortCreatedAt }] : []),
|
||||
...(sortUpdatedAt ? [{ updatedAt: sortUpdatedAt }] : []),
|
||||
],
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { RoutingFormResponseOutput, RoutingFormResponseResponseOutput } from "@c
|
||||
import { App_RoutingForms_FormResponse } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class RoutingFormsResponsesOutputService {
|
||||
export class OrganizationsTeamsRoutingFormsResponsesOutputService {
|
||||
getRoutingFormResponses(
|
||||
dbRoutingFormResponses: App_RoutingForms_FormResponse[]
|
||||
): RoutingFormResponseOutput[] {
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
import { OrganizationsTeamsRoutingFormsResponsesOutputService } from "@/modules/organizations/teams/routing-forms/services/organizations-teams-routing-forms-responses-output.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsTeamsRoutingFormsResponsesRepository } from "../repositories/organizations-teams-routing-forms-responses.repository";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsRoutingFormsResponsesService {
|
||||
constructor(
|
||||
private readonly routingFormsRepository: OrganizationsTeamsRoutingFormsResponsesRepository,
|
||||
private readonly routingFormsResponsesOutputService: OrganizationsTeamsRoutingFormsResponsesOutputService
|
||||
) {}
|
||||
|
||||
async getTeamRoutingFormResponses(
|
||||
teamId: number,
|
||||
routingFormId: string,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
routedToBookingUid?: string;
|
||||
}
|
||||
) {
|
||||
const responses = await this.routingFormsRepository.getTeamRoutingFormResponses(
|
||||
teamId,
|
||||
routingFormId,
|
||||
skip,
|
||||
take,
|
||||
options
|
||||
);
|
||||
|
||||
return this.routingFormsResponsesOutputService.getRoutingFormResponses(responses);
|
||||
}
|
||||
|
||||
async updateTeamRoutingFormResponse(
|
||||
teamId: number,
|
||||
routingFormId: string,
|
||||
responseId: number,
|
||||
data: {
|
||||
response?: Record<string, any>;
|
||||
}
|
||||
) {
|
||||
const updatedResponse = await this.routingFormsRepository.updateTeamRoutingFormResponse(
|
||||
teamId,
|
||||
routingFormId,
|
||||
responseId,
|
||||
data
|
||||
);
|
||||
|
||||
return this.routingFormsResponsesOutputService.getRoutingFormResponses([updatedResponse])[0];
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { OrganizationsTeamsRoutingFormsRepository } from "../repositories/organizations-teams-routing-forms.repository";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsTeamsRoutingFormsService {
|
||||
constructor(private readonly routingFormsRepository: OrganizationsTeamsRoutingFormsRepository) {}
|
||||
|
||||
async getTeamRoutingForms(
|
||||
teamId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
options?: {
|
||||
disabled?: boolean;
|
||||
name?: string;
|
||||
sortCreatedAt?: "asc" | "desc";
|
||||
sortUpdatedAt?: "asc" | "desc";
|
||||
afterCreatedAt?: Date;
|
||||
beforeCreatedAt?: Date;
|
||||
afterUpdatedAt?: Date;
|
||||
beforeUpdatedAt?: Date;
|
||||
}
|
||||
) {
|
||||
return this.routingFormsRepository.getTeamRoutingForms(teamId, skip, take, options);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RoutingFormsResponsesRepository } from "@/modules/routing-forms-responses/routing-forms-responses.repository";
|
||||
import { RoutingFormsResponsesOutputService } from "@/modules/routing-forms-responses/services/routing-forms-responses-output.service";
|
||||
import { RoutingFormsResponsesService } from "@/modules/routing-forms-responses/services/routing-forms-responses.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [
|
||||
RoutingFormsResponsesService,
|
||||
RoutingFormsResponsesRepository,
|
||||
RoutingFormsResponsesOutputService,
|
||||
],
|
||||
exports: [
|
||||
RoutingFormsResponsesService,
|
||||
RoutingFormsResponsesRepository,
|
||||
RoutingFormsResponsesOutputService,
|
||||
],
|
||||
})
|
||||
export class RoutingFormsResponsesModule {}
|
||||
@@ -1,18 +0,0 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class RoutingFormsResponsesRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService) {}
|
||||
|
||||
async getRoutingFormResponses(routingFormId: string) {
|
||||
return this.dbRead.prisma.app_RoutingForms_FormResponse.findMany({
|
||||
where: {
|
||||
formId: routingFormId,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
import { RoutingFormsResponsesOutputService } from "@/modules/routing-forms-responses/services/routing-forms-responses-output.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { RoutingFormsResponsesRepository } from "../routing-forms-responses.repository";
|
||||
|
||||
@Injectable()
|
||||
export class RoutingFormsResponsesService {
|
||||
constructor(
|
||||
private readonly routingFormsRepository: RoutingFormsResponsesRepository,
|
||||
private readonly routingFormsResponsesOutputService: RoutingFormsResponsesOutputService
|
||||
) {}
|
||||
|
||||
async getRoutingFormResponses(routingFormId: string) {
|
||||
const responses = await this.routingFormsRepository.getRoutingFormResponses(routingFormId);
|
||||
return this.routingFormsResponsesOutputService.getRoutingFormResponses(responses);
|
||||
}
|
||||
}
|
||||
@@ -2291,6 +2291,358 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsRoutingFormsController_getOrganizationRoutingForms",
|
||||
"summary": "Get organization routing forms",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamIds",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by teamIds",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormsOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Orgs / Routing forms"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms/{routingFormId}/responses": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsRoutingFormsResponsesController_getRoutingFormResponses",
|
||||
"summary": "Get routing form responses",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormResponsesOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Orgs / Routing forms"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms/{routingFormId}/responses/{responseId}": {
|
||||
"patch": {
|
||||
"operationId": "OrganizationsRoutingFormsResponsesController_updateRoutingFormResponse",
|
||||
"summary": "Update routing form response",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "responseId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Orgs / Routing forms"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/schedules": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsSchedulesController_getOrganizationSchedules",
|
||||
@@ -3886,10 +4238,151 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsController_getTeamRoutingForms",
|
||||
"summary": "Get team routing forms",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormsOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Orgs / Teams / Routing forms"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsResponsesController_getRoutingFormResponses",
|
||||
"summary": "Get routing form responses",
|
||||
"summary": "Get organization team routing form responses",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
@@ -3907,6 +4400,115 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": [
|
||||
"asc",
|
||||
"desc"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3926,6 +4528,72 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses/{responseId}": {
|
||||
"patch": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsResponsesController_updateRoutingFormResponse",
|
||||
"summary": "Update routing form response",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "responseId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Orgs / Teams / Routing forms / Responses"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/users/{userId}/schedules": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsSchedulesController_getUserSchedules",
|
||||
@@ -19149,6 +19817,123 @@
|
||||
]
|
||||
},
|
||||
"GetRoutingFormResponsesOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"UpdateRoutingFormResponseInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"response": {
|
||||
"type": "object",
|
||||
"description": "The updated response data"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpdateRoutingFormResponseOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"RoutingFormOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "My Form"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "This is the description."
|
||||
},
|
||||
"position": {
|
||||
"type": "number",
|
||||
"example": 0
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"example": "2024-03-28T10:00:00.000Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"example": "2024-03-28T10:00:00.000Z"
|
||||
},
|
||||
"userId": {
|
||||
"type": "number",
|
||||
"example": 2313
|
||||
},
|
||||
"teamId": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"example": 4214321
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description",
|
||||
"position",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"userId",
|
||||
"teamId",
|
||||
"disabled",
|
||||
"id",
|
||||
"routes",
|
||||
"fields",
|
||||
"settings"
|
||||
]
|
||||
},
|
||||
"GetRoutingFormsOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
@@ -19162,7 +19947,7 @@
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
"$ref": "#/components/schemas/RoutingFormOutput"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2197,6 +2197,340 @@
|
||||
"tags": ["Orgs / Orgs"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsRoutingFormsController_getOrganizationRoutingForms",
|
||||
"summary": "Get organization routing forms",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamIds",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by teamIds",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormsOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["Orgs / Routing forms"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms/{routingFormId}/responses": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsRoutingFormsResponsesController_getRoutingFormResponses",
|
||||
"summary": "Get routing form responses",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormResponsesOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["Orgs / Routing forms"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/routing-forms/{routingFormId}/responses/{responseId}": {
|
||||
"patch": {
|
||||
"operationId": "OrganizationsRoutingFormsResponsesController_updateRoutingFormResponse",
|
||||
"summary": "Update routing form response",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "responseId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["Orgs / Routing forms"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/schedules": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsSchedulesController_getOrganizationSchedules",
|
||||
@@ -3737,10 +4071,143 @@
|
||||
"tags": ["Orgs / Teams / Memberships"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsController_getTeamRoutingForms",
|
||||
"summary": "Get team routing forms",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetRoutingFormsOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["Orgs / Teams / Routing forms"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsResponsesController_getRoutingFormResponses",
|
||||
"summary": "Get routing form responses",
|
||||
"summary": "Get organization team routing form responses",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
@@ -3758,6 +4225,109 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "orgId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to skip",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Number of responses to take",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by creation time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "sortUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Sort by update time",
|
||||
"schema": {
|
||||
"enum": ["asc", "desc"],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeCreatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "afterUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses created after this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "beforeUpdatedAt",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses updated before this date",
|
||||
"schema": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routedToBookingUid",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "Filter by responses routed to a specific booking",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3775,6 +4345,70 @@
|
||||
"tags": ["Orgs / Teams / Routing forms / Responses"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/routing-forms/{routingFormId}/responses/{responseId}": {
|
||||
"patch": {
|
||||
"operationId": "OrganizationsTeamsRoutingFormsResponsesController_updateRoutingFormResponse",
|
||||
"summary": "Update routing form response",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"description": "value must be `Bearer <token>` where `<token>` is api key prefixed with cal_",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "teamId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "routingFormId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "responseId",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdateRoutingFormResponseOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["Orgs / Teams / Routing forms / Responses"]
|
||||
}
|
||||
},
|
||||
"/v2/organizations/{orgId}/teams/{teamId}/users/{userId}/schedules": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsSchedulesController_getUserSchedules",
|
||||
@@ -17462,6 +18096,111 @@
|
||||
"required": ["id", "formId", "formFillerId", "routedToBookingUid", "response", "createdAt"]
|
||||
},
|
||||
"GetRoutingFormResponsesOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": ["success", "error"]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
}
|
||||
},
|
||||
"required": ["status", "data"]
|
||||
},
|
||||
"UpdateRoutingFormResponseInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"response": {
|
||||
"type": "object",
|
||||
"description": "The updated response data"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UpdateRoutingFormResponseOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": ["success", "error"]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
}
|
||||
},
|
||||
"required": ["status", "data"]
|
||||
},
|
||||
"RoutingFormOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "My Form"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"example": "This is the description."
|
||||
},
|
||||
"position": {
|
||||
"type": "number",
|
||||
"example": 0
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"example": "2024-03-28T10:00:00.000Z"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"example": "2024-03-28T10:00:00.000Z"
|
||||
},
|
||||
"userId": {
|
||||
"type": "number",
|
||||
"example": 2313
|
||||
},
|
||||
"teamId": {
|
||||
"type": "number",
|
||||
"nullable": true,
|
||||
"example": 4214321
|
||||
},
|
||||
"disabled": {
|
||||
"type": "boolean",
|
||||
"example": false
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"routes": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
},
|
||||
"fields": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"nullable": true
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description",
|
||||
"position",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"userId",
|
||||
"teamId",
|
||||
"disabled",
|
||||
"id",
|
||||
"routes",
|
||||
"fields",
|
||||
"settings"
|
||||
]
|
||||
},
|
||||
"GetRoutingFormsOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
@@ -17472,7 +18211,7 @@
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/RoutingFormResponseOutput"
|
||||
"$ref": "#/components/schemas/RoutingFormOutput"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `updatedAt` to the `App_RoutingForms_FormResponse` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "App_RoutingForms_FormResponse" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL;
|
||||
@@ -1061,15 +1061,17 @@ model App_RoutingForms_Form {
|
||||
}
|
||||
|
||||
model App_RoutingForms_FormResponse {
|
||||
id Int @id @default(autoincrement())
|
||||
formFillerId String @default(cuid())
|
||||
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
formId String
|
||||
response Json
|
||||
createdAt DateTime @default(now())
|
||||
routedToBookingUid String? @unique
|
||||
id Int @id @default(autoincrement())
|
||||
formFillerId String @default(cuid())
|
||||
form App_RoutingForms_Form @relation(fields: [formId], references: [id], onDelete: Cascade)
|
||||
formId String
|
||||
response Json
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
routedToBookingUid String? @unique
|
||||
// We should not cascade delete the booking, because we want to keep the form response even if the routedToBooking is deleted
|
||||
routedToBooking Booking? @relation(fields: [routedToBookingUid], references: [uid])
|
||||
routedToBooking Booking? @relation(fields: [routedToBookingUid], references: [uid])
|
||||
chosenRouteId String?
|
||||
|
||||
@@unique([formFillerId, formId])
|
||||
|
||||
Reference in new issue
Block a user