Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
240f27ef4c | ||
|
|
ab96707a0e | ||
|
|
7f86f69528 | ||
|
|
93da9c547a | ||
|
|
4475509225 | ||
|
|
1043085741 | ||
|
|
4fd5b3133d | ||
|
|
ca650babaf | ||
|
|
f0dc3bba17 | ||
|
|
15d6839985 | ||
|
|
c20b3419d4 | ||
|
|
b280e78da8 | ||
|
|
3ca44cff0c | ||
|
|
8dd05dda70 | ||
|
|
dde76ed74d | ||
|
|
564cc3a394 | ||
|
|
7bbacca32a | ||
|
|
b7df700771 | ||
|
|
fdc8e084ea | ||
|
|
195e645f24 | ||
|
|
82b1e681de | ||
|
|
024d47cbc0 | ||
|
|
56cd9cef8b | ||
|
|
8d2dc0bbe5 | ||
|
|
a0f6e50d5b | ||
|
|
7ec928f5c2 | ||
|
|
06729cfc9a | ||
|
|
cdc3c49b23 | ||
|
|
962f692e29 | ||
|
|
783f10ffb5 | ||
|
|
b6a7a8f75a | ||
|
|
1e610ea684 | ||
|
|
5c98861f6f | ||
|
|
be1b9d1a79 | ||
|
|
ac8b4a2861 | ||
|
|
33903f1a03 | ||
|
|
92678dc042 | ||
|
|
8615ccb1aa | ||
|
|
9608669f98 | ||
|
|
f8fa8ff294 | ||
|
|
2a5e93b015 | ||
|
|
e23ab02c23 | ||
|
|
8205431640 | ||
|
|
9cec970943 | ||
|
|
296a09dc7b | ||
|
|
377d4e5bfd | ||
|
|
ce4c9dfc9a | ||
|
|
438a73b0cd | ||
|
|
39891ee308 | ||
|
|
2646f57da4 | ||
|
|
f8a443557a | ||
|
|
fa82ecd2a5 | ||
|
|
8f8af6536c | ||
|
|
9ad45bb477 | ||
|
|
a1e4959fb7 | ||
|
|
e5946db179 | ||
|
|
f2d12a4c21 | ||
|
|
d8c9d2ad94 | ||
|
|
cc94fb4a4b | ||
|
|
cd413cf640 | ||
|
|
7d4abea9b4 | ||
|
|
e0527587c1 | ||
|
|
ae51c8b599 | ||
|
|
7fb60fe5f7 | ||
|
|
4a93511f50 | ||
|
|
61a2a3ef5a | ||
|
|
f06bac7129 | ||
|
|
0ecc90ccb4 | ||
|
|
ee31633e2b | ||
|
|
8be2ea8117 | ||
|
|
df4359e3e5 | ||
|
|
d3ef807af5 | ||
|
|
41f63582b2 | ||
|
|
3f622fe277 | ||
|
|
ed3579c5c7 | ||
|
|
2f9beed450 | ||
|
|
2b79ef0836 | ||
|
|
828940b4d6 | ||
|
|
0dab3e1a6d | ||
|
|
c41396d8d8 | ||
|
|
c5d0d66a26 | ||
|
|
30e576377a | ||
|
|
4235a36887 | ||
|
|
b234b16ad0 | ||
|
|
3586046786 | ||
|
|
d5e633b481 | ||
|
|
a069af715e | ||
|
|
beb542e7cb | ||
|
|
84df8dc6eb | ||
|
|
38e7eb03f9 | ||
|
|
656d6dc195 | ||
|
|
1fd4c3fd4c | ||
|
|
b3f946dd0b | ||
|
|
161aece3a1 | ||
|
|
fec8019775 | ||
|
|
91225c03f3 | ||
|
|
81daa6581f | ||
|
|
b304467f15 | ||
|
|
e748d3e252 | ||
|
|
f8630cebf4 | ||
|
|
36d756e469 | ||
|
|
8e191fed71 | ||
|
|
2335aba843 | ||
|
|
1ffea493ec | ||
|
|
3765c76c96 | ||
|
|
1af3caa8cc | ||
|
|
35565c8f7e | ||
|
|
0f07ab2f7b | ||
|
|
f0d1c92e99 | ||
|
|
d1134c8560 | ||
|
|
586b3532b5 | ||
|
|
15e7fc192e | ||
|
|
95bf90402c | ||
|
|
f523ce7795 | ||
|
|
65b70d682d | ||
|
|
2d518f49dd | ||
|
|
7a9d291d1f | ||
|
|
1a1a506324 | ||
|
|
0f7ecb3345 | ||
|
|
4f1d71e660 | ||
|
|
30eb7ce846 | ||
|
|
3eb5490df9 | ||
|
|
235382213f | ||
|
|
01baf7d3c0 | ||
|
|
1a649158e1 | ||
|
|
d6306dcd22 | ||
|
|
7131a1f015 | ||
|
|
5357228f72 | ||
|
|
bfeafaa21f | ||
|
|
02ef6dc934 | ||
|
|
4424b2aea3 | ||
|
|
1f91fe888a | ||
|
|
b4a51d3560 | ||
|
|
4855b6b40f | ||
|
|
4b264a867b | ||
|
|
9caaa0af32 | ||
|
|
f411c6ed11 | ||
|
|
245196fbf3 | ||
|
|
6be69b820c | ||
|
|
b63c9791ec | ||
|
|
8cc61a1fe9 | ||
|
|
be2e1274ff | ||
|
|
fa3b1c17ea | ||
|
|
015edd3c9b | ||
|
|
de05e8e989 | ||
|
|
78a6963cd9 | ||
|
|
ea827ebbc6 | ||
|
|
08e58851e7 | ||
|
|
da1b8137c2 | ||
|
|
d182e5c99a | ||
|
|
42259a113b | ||
|
+8 |
6561b92f40 | ||
|
|
9e5665f2fb | ||
|
|
07778d22ed | ||
|
|
56df8bdf7c | ||
|
|
a518c7152a | ||
|
|
c37a8a587b | ||
|
|
b7f7c33afc | ||
|
|
1d449ecc6b | ||
|
|
f582faed2b | ||
|
|
79a8ab79f8 | ||
|
|
ce87de4fc7 | ||
|
|
b128579729 | ||
|
|
3cc5d9c065 | ||
|
|
7897e14273 | ||
|
|
c581456586 | ||
|
|
36dd3e9e6c | ||
|
|
f137aee436 | ||
|
|
4f494d5a32 | ||
|
|
cce6e83578 | ||
|
|
c21ba636d2 | ||
|
|
5ccf5e6be8 | ||
|
|
66b3e735d8 | ||
|
|
7a8b9ededb | ||
|
|
501c49f369 | ||
|
|
536604455b | ||
|
|
cfb76a9689 | ||
|
|
30180edc0b | ||
|
|
2aa3d1d4e0 | ||
|
|
21694c039a | ||
|
|
a8c29b5459 | ||
|
|
6cd7454f43 | ||
|
|
99b2419dd7 | ||
|
|
f6d0a2ca09 | ||
|
|
f5ab9bd40b | ||
|
|
07085b9ffb | ||
|
|
14151b827f | ||
|
|
28b757f19c | ||
|
|
68455db860 | ||
|
|
96ff88d266 | ||
|
|
f88a91dad0 | ||
|
|
35059d7727 | ||
|
|
68f76e2acc | ||
|
|
80aac06ff3 | ||
|
|
0f47528ba2 | ||
|
|
113dc5fca6 | ||
|
|
0befdde5d7 | ||
|
|
b3ed6e39e3 | ||
|
|
e07b1eda32 | ||
|
|
8262894bde | ||
|
|
a3d7bcd22e | ||
|
|
62b753afba | ||
|
|
bb40b2f6bf | ||
|
|
f628dc83a8 | ||
|
|
2fd2578504 | ||
|
|
a29407349d | ||
|
|
e7df1ef493 | ||
|
|
b652ae7714 | ||
|
|
cec1c9ee46 | ||
|
|
3813fd70f7 | ||
|
|
dabd3b0fc3 | ||
|
|
95f9a02d29 | ||
|
|
f26d2998cb | ||
|
|
f558f8aca5 | ||
|
|
a6dd5185ea | ||
|
|
3da7694002 | ||
|
|
1ffb376789 | ||
|
|
8761b91f71 | ||
|
|
1dbef7f8f6 | ||
|
|
d4d5ac2b28 | ||
|
|
88c8ad8b5c | ||
|
|
0a08680084 | ||
|
|
65828545b9 | ||
|
|
3adb8e84fc | ||
|
|
2a29155641 | ||
|
|
4902c6a0f8 | ||
|
|
a22fd0c151 | ||
|
|
b6bd0eda43 | ||
|
|
25d052e8f0 | ||
|
|
f933432d2b | ||
|
|
ccf1d4430a | ||
|
|
2fd8d24204 | ||
|
|
b4c18f7bcc | ||
|
|
23c93d75ca | ||
|
|
91526d0bc7 | ||
|
|
a9f95937c4 | ||
|
|
74580157ba | ||
|
|
fcdbcceac0 | ||
|
|
aa7c924c2a | ||
|
|
3406ca72bc | ||
|
|
c29a2fc7cb | ||
|
|
0a4c7bcecc | ||
|
|
803dfa008c | ||
|
|
0eb6f1beb6 | ||
|
|
edd2e5e584 | ||
|
|
99a99b4b7e | ||
|
|
2ba6f4cb24 | ||
|
|
8a8c870513 | ||
|
|
eba335c903 | ||
|
|
9b2161cc19 | ||
|
|
b997c656df | ||
|
|
c9c5493de6 | ||
|
|
20a5775f31 | ||
|
|
b9b0a86b32 | ||
|
|
ad93066cf6 | ||
|
|
e6d76b630c | ||
|
|
b941b716d5 | ||
|
|
1ab9622416 | ||
|
|
15156c2225 | ||
|
|
cc202ed847 | ||
|
|
79d51a4989 | ||
|
|
53a846d10c |
+3
-17
@@ -112,7 +112,7 @@ NEXT_PUBLIC_POSTHOG_HOST=
|
||||
PLAIN_API_KEY=
|
||||
PLAIN_API_URL=https://api.plain.com/v1
|
||||
PLAIN_HMAC_SECRET_KEY=
|
||||
PLAIN_CHAT_ID=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_ID=
|
||||
PLAIN_CHAT_HMAC_SECRET_KEY=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS=
|
||||
|
||||
@@ -242,6 +242,7 @@ E2E_TEST_MAILHOG_ENABLED=
|
||||
|
||||
# Cloudflare Turnstile
|
||||
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
||||
NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=
|
||||
CLOUDFLARE_TURNSTILE_SECRET=
|
||||
|
||||
# Set the following value to true if you wish to enable Team Impersonation
|
||||
@@ -355,24 +356,9 @@ E2E_TEST_OIDC_USER_PASSWORD=
|
||||
# provide a value between 0 and 100 to ensure the percentage of traffic
|
||||
# redirected from the legacy to the future pages
|
||||
AB_TEST_BUCKET_PROBABILITY=50
|
||||
# whether we redirect to the future/event-types from event-types or not
|
||||
APP_ROUTER_EVENT_TYPES_ENABLED=0
|
||||
APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0
|
||||
APP_ROUTER_APPS_SLUG_ENABLED=0
|
||||
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
||||
# whether we redirect to the future/apps/categories from /apps/categories or not
|
||||
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
||||
APP_ROUTER_APPS_ENABLED=0
|
||||
APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGIN_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGOUT_ENABLED=0
|
||||
APP_ROUTER_AUTH_NEW_ENABLED=0
|
||||
APP_ROUTER_AUTH_SAML_ENABLED=0
|
||||
APP_ROUTER_AUTH_ERROR_ENABLED=0
|
||||
APP_ROUTER_AUTH_PLATFORM_ENABLED=0
|
||||
APP_ROUTER_AUTH_OAUTH2_ENABLED=0
|
||||
APP_ROUTER_TEAM_ENABLED=0
|
||||
|
||||
# disable setry server source maps
|
||||
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# .github/workflows/chromatic.yml
|
||||
|
||||
# Workflow name
|
||||
name: "Chromatic"
|
||||
|
||||
# Event for the workflow
|
||||
on:
|
||||
pull_request_target: # So we can test on forks
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- apps/storybook/**
|
||||
- packages/ui/**
|
||||
workflow_dispatch:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# List of jobs
|
||||
jobs:
|
||||
chromatic-deployment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
|
||||
fetch-depth: 0
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
# 👇 Adds Chromatic as a step in the workflow
|
||||
- name: Publish to Chromatic
|
||||
uses: chromaui/action@v1
|
||||
# Options required to the GitHub Chromatic Action
|
||||
with:
|
||||
# 👇 Chromatic projectToken, refer to the manage page to obtain it.
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
workingDir: "apps/storybook"
|
||||
buildScriptName: "build"
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: replexica/replexica@main
|
||||
- uses: lingodotdev/lingo.dev@main
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -12,16 +12,6 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Run Linting with Reports
|
||||
run: yarn lint:report
|
||||
- name: Run Lint
|
||||
run: yarn lint
|
||||
continue-on-error: true
|
||||
|
||||
- name: Merge lint reports
|
||||
run: jq -s '[.[]]|flatten' lint-results/*.json &> lint-results/eslint_report.json
|
||||
|
||||
- name: Upload ESLint report
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: lint-results
|
||||
path: lint-results
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
diff --git a/index.cjs b/index.cjs
|
||||
index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67746ad5a4 100644
|
||||
index c83f700..da6fc7e 100644
|
||||
--- a/index.cjs
|
||||
+++ b/index.cjs
|
||||
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
|
||||
@@ -12,4 +12,4 @@ index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67
|
||||
+// exports['default'] = min.parsePhoneNumberFromString
|
||||
|
||||
// `parsePhoneNumberFromString()` named export is now considered legacy:
|
||||
// it has been promoted to a default export due to being too verbose.
|
||||
// it has been promoted to a default export due to being too verbose.
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/prisma"
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/prisma"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
@@ -14,7 +14,7 @@ test.describe("Org", () => {
|
||||
test("Org User(Rick) Page should be embeddable", async ({ page }) => {
|
||||
const response = await page.goto("https://i.cal.com/team-rick/embed");
|
||||
expect(response?.status()).toBe(200);
|
||||
await expect(page.locator("text=Rick in i.cal.com")).toBeVisible();
|
||||
await expect(page.locator("text=Used by Checkly")).toBeVisible();
|
||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||
});
|
||||
|
||||
@@ -58,10 +58,10 @@ test.describe("Org", () => {
|
||||
expect(response?.status()).toBe(200);
|
||||
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Group Meeting");
|
||||
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[0].name);
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
||||
// 2 users and 1 for the organization(2+1)
|
||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(
|
||||
"Join us for a meeting with multiple people"
|
||||
);
|
||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,7 +75,9 @@ test.describe("Org", () => {
|
||||
|
||||
test.describe("Browse the Engineering Team", async () => {
|
||||
test("By User Navigation", async ({ page }) => {
|
||||
await page.goto("https://i.cal.com");
|
||||
const response = await page.goto("https://i.cal.com");
|
||||
await page.waitForLoadState("networkidle");
|
||||
expect(response?.status()).toBe(200);
|
||||
await page.click('text="Engineering"');
|
||||
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
import type { RatelimitResponse } from "@unkey/ratelimit";
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiResponse, NextApiRequest } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
const testUserId = 123;
|
||||
|
||||
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
|
||||
checkRateLimitAndThrowError: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/autoLock", () => ({
|
||||
handleAutoLock: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("rateLimitApiKey middleware", () => {
|
||||
it("should return 401 if no apiKey is provided", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
query: {},
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
@@ -31,6 +41,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
||||
@@ -43,7 +54,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||
identifier: "test-key",
|
||||
identifier: testUserId.toString(),
|
||||
rateLimitingType: "api",
|
||||
onRateLimiterResponse: expect.any(Function),
|
||||
});
|
||||
@@ -53,19 +64,23 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse = {
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
limit: 100,
|
||||
remaining: 99,
|
||||
reset: Date.now(),
|
||||
success: true,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
});
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
@@ -79,6 +94,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
||||
@@ -89,4 +105,139 @@ describe("rateLimitApiKey middleware", () => {
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Rate limit exceeded" });
|
||||
});
|
||||
|
||||
it("should lock API key when rate limit is repeatedly exceeded", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to indicate the key was locked
|
||||
vi.mocked(handleAutoLock).mockResolvedValueOnce(true);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Too many requests" });
|
||||
});
|
||||
|
||||
it("should handle API key not found error during auto-lock", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to throw a "No user found" error
|
||||
vi.mocked(handleAutoLock).mockRejectedValueOnce(new Error("No user found for this API key."));
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
expect(res._getStatusCode()).toBe(401);
|
||||
expect(res._getJSONData()).toEqual({ message: "No user found for this API key." });
|
||||
});
|
||||
|
||||
it("should continue if auto-lock returns false (not locked)", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to indicate the key was not locked
|
||||
vi.mocked(handleAutoLock).mockResolvedValueOnce(false);
|
||||
|
||||
const next = vi.fn();
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, next);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
// Verify headers were set but request continued
|
||||
expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle HttpError during rate limiting", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
// Mock checkRateLimitAndThrowError to throw HttpError
|
||||
vi.mocked(checkRateLimitAndThrowError).mockRejectedValueOnce(
|
||||
new HttpError({
|
||||
statusCode: 429,
|
||||
message: "Custom rate limit error",
|
||||
})
|
||||
);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Custom rate limit error" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
import type { NextMiddleware } from "next-api-middleware";
|
||||
|
||||
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||
if (!req.userId) return res.status(401).json({ message: "No userId provided" });
|
||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||
|
||||
// TODO: Add a way to add trusted api keys
|
||||
try {
|
||||
const identifier = req.userId.toString();
|
||||
await checkRateLimitAndThrowError({
|
||||
identifier: req.query.apiKey as string,
|
||||
identifier,
|
||||
rateLimitingType: "api",
|
||||
onRateLimiterResponse: (response) => {
|
||||
onRateLimiterResponse: async (response) => {
|
||||
res.setHeader("X-RateLimit-Limit", response.limit);
|
||||
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||
res.setHeader("X-RateLimit-Reset", response.reset);
|
||||
|
||||
try {
|
||||
const didLock = await handleAutoLock({
|
||||
identifier,
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: response,
|
||||
});
|
||||
|
||||
if (didLock) {
|
||||
return res.status(429).json({ message: "Too many requests" });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "No user found for this API key.") {
|
||||
return res.status(401).json({ message: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(429).json({ message: "Rate limit exceeded" });
|
||||
if (error instanceof HttpError) {
|
||||
return res.status(error.statusCode).json({ message: error.message });
|
||||
}
|
||||
return res.status(429).json({ message: "Rate limit exceeded" });
|
||||
}
|
||||
|
||||
await next();
|
||||
|
||||
@@ -38,6 +38,23 @@ export const schemaBookingGetParams = z.object({
|
||||
|
||||
export type Status = z.infer<typeof schemaBookingGetParams>["status"];
|
||||
|
||||
export const bookingCancelSchema = z.object({
|
||||
id: z.number(),
|
||||
allRemainingBookings: z.boolean().optional(),
|
||||
cancelSubsequentBookings: z.boolean().optional(),
|
||||
cancellationReason: z.string().optional().default("Not Provided"),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
cancelledBy: z.string().email({ message: "Invalid email" }).optional(),
|
||||
internalNote: z
|
||||
.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
cancellationReason: z.string().optional().nullable(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
const schemaBookingEditParams = z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Helper schema for JSON fields
|
||||
type Literal = boolean | number | string;
|
||||
type Literal = boolean | number | string | null;
|
||||
type Json = Literal | { [key: string]: Json } | Json[];
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean()]);
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
const jsonObjectSchema = z.record(z.lazy(() => jsonSchema));
|
||||
const jsonArraySchema = z.array(z.lazy(() => jsonSchema));
|
||||
export const jsonSchema: z.ZodSchema<Json> = z.lazy(() =>
|
||||
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
|
||||
z.union([literalSchema, jsonObjectSchema, jsonArraySchema])
|
||||
);
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { bookingCancelSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { bookingCancelSchema } from "~/lib/validations/booking";
|
||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,7 @@ async function handler(req: NextApiRequest) {
|
||||
...req.query,
|
||||
allRemainingBookings: req.query.allRemainingBookings === "true",
|
||||
});
|
||||
|
||||
// Normalizing for universal handler
|
||||
req.body = { id, allRemainingBookings, cancellationReason };
|
||||
return await handleCancelBooking(req);
|
||||
|
||||
@@ -5,6 +5,7 @@ import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
|
||||
@@ -213,6 +214,10 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
*/
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||
req.body = {
|
||||
...req.body,
|
||||
creationSource: CreationSource.API_V1,
|
||||
};
|
||||
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||
|
||||
if (isOrganizationOwnerOrAdmin) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||
@@ -43,6 +44,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
language: data.language,
|
||||
teamId: data.teamId,
|
||||
usernameOrEmail: data.usernameOrEmail,
|
||||
creationSource: CreationSource.API_V1,
|
||||
});
|
||||
|
||||
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
|
||||
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||
import { getDubCustomer } from "@calcom/features/auth/lib/dub";
|
||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
@@ -192,11 +193,26 @@ const generateTeamCheckoutSession = async ({
|
||||
pendingPaymentTeamId: number;
|
||||
ownerId: number;
|
||||
}) => {
|
||||
const customer = await getStripeCustomerIdFromUserId(ownerId);
|
||||
const [customer, dubCustomer] = await Promise.all([
|
||||
getStripeCustomerIdFromUserId(ownerId),
|
||||
getDubCustomer(ownerId.toString()),
|
||||
]);
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
customer,
|
||||
mode: "subscription",
|
||||
allow_promotion_codes: true,
|
||||
...(dubCustomer?.discount?.couponId
|
||||
? {
|
||||
discounts: [
|
||||
{
|
||||
coupon:
|
||||
process.env.NODE_ENV !== "production" && dubCustomer.discount.couponTestId
|
||||
? dubCustomer.discount.couponTestId
|
||||
: dubCustomer.discount.couponId,
|
||||
},
|
||||
],
|
||||
}
|
||||
: { allow_promotion_codes: true }),
|
||||
success_url: `${WEBAPP_URL}/api/teams/api/create?session_id={CHECKOUT_SESSION_ID}`,
|
||||
cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
|
||||
line_items: [
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest } from "next";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { schemaUserCreateBodyParams } from "~/lib/validations/user";
|
||||
|
||||
@@ -92,7 +93,7 @@ async function postHandler(req: NextApiRequest) {
|
||||
// If user is not ADMIN, return unauthorized.
|
||||
if (!isSystemWideAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" });
|
||||
const data = await schemaUserCreateBodyParams.parseAsync(req.body);
|
||||
const user = await prisma.user.create({ data });
|
||||
const user = await prisma.user.create({ data: { ...data, creationSource: CreationSource.API_V1 } });
|
||||
req.statusCode = 201;
|
||||
return { user };
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Booking } from "@calcom/prisma/client";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import handler from "../../../pages/api/bookings/_post";
|
||||
|
||||
@@ -220,6 +221,34 @@ describe.skipIf(true)("POST /api/bookings", () => {
|
||||
});
|
||||
expect(previousBooking?.status).toBe("cancelled");
|
||||
});
|
||||
|
||||
test("Creates source as api_v1", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
name: "test",
|
||||
start: dayjs().format(),
|
||||
end: dayjs().add(1, "day").format(),
|
||||
eventTypeId: 2,
|
||||
email: "test@example.com",
|
||||
location: "Cal.com Video",
|
||||
timeZone: "America/Montevideo",
|
||||
language: "en",
|
||||
customInputs: [],
|
||||
metadata: {},
|
||||
userId: 4,
|
||||
},
|
||||
prisma,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType());
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
await handler(req, res);
|
||||
createdBooking = JSON.parse(res._getData());
|
||||
expect(createdBooking.creationSource).toEqual(CreationSource.API_V1);
|
||||
expect(prismaMock.booking.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Recurring event-type", () => {
|
||||
|
||||
@@ -13,5 +13,5 @@
|
||||
"setupFiles": ["<rootDir>/test/setEnvVars.ts", "jest-date-mock"],
|
||||
"reporters": ["default", "jest-summarizing-reporter"],
|
||||
"workerIdleMemoryLimit": "512MB",
|
||||
"maxWorkers": 2
|
||||
"maxWorkers": 8
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"test:cov": "yarn dev:build && jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "yarn dev:build && NODE_OPTIONS='--max_old_space_size=8192 --experimental-vm-modules' jest --ci --forceExit --config ./jest-e2e.json",
|
||||
"test:e2e:local": "yarn test:e2e --maxWorkers=4",
|
||||
"test:e2e:watch": "yarn dev:build && jest --runInBand --detectOpenHandles --forceExit --config ./jest-e2e.json --watch",
|
||||
"prisma": "yarn workspace @calcom/prisma prisma",
|
||||
"generate-schemas": "yarn prisma generate && yarn prisma format",
|
||||
@@ -29,7 +30,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.79",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.93",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
@@ -59,6 +60,7 @@
|
||||
"googleapis": "^84.0.0",
|
||||
"helmet": "^7.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"jsforce": "^1.11.0",
|
||||
"luxon": "^3.4.4",
|
||||
"nest-winston": "^1.9.4",
|
||||
"next-auth": "^4.22.1",
|
||||
|
||||
@@ -14,6 +14,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { RateLimitRepositoryFixture } from "test/fixtures/repository/rate-limit.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import { User, PlatformOAuthClient, Team, RateLimit } from "@calcom/prisma/client";
|
||||
@@ -24,7 +25,7 @@ describe("AppController", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let rateLimitRepositoryFixture: RateLimitRepositoryFixture;
|
||||
const userEmail = "app-rate-limits-e2e@api.com";
|
||||
const userEmail = `app-rate-limits-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let organization: Team;
|
||||
@@ -94,7 +95,9 @@ describe("AppController", () => {
|
||||
);
|
||||
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
organization = await organizationsRepositoryFixture.create({ name: "ecorp" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `app-rate-limits-organization-${randomString()}`,
|
||||
});
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
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";
|
||||
@@ -18,6 +19,7 @@ import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repo
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
@@ -35,7 +37,7 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let apiKeyString: string;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `bookings-2024-04-15-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
@@ -67,13 +69,17 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = keyString;
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `bookings-2024-04-15-schedule-${randomString()}-${describe.name}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `bookings-2024-04-15-event-type-${randomString()}-${describe.name}`,
|
||||
slug: `bookings-2024-04-15-event-type-${randomString()}-${describe.name}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
@@ -343,94 +349,6 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// note(Lauris) : found this test broken here - first thing to fix is that recurring endpoint accepts an array not 1 object.
|
||||
// it("should create a recurring booking", async () => {
|
||||
// const bookingStart = "2040-05-25T09:30:00.000Z";
|
||||
// const bookingEnd = "2040-05-25T10:30:00.000Z";
|
||||
// const bookingEventTypeId = 7;
|
||||
// const bookingTimeZone = "Europe/London";
|
||||
// const bookingLanguage = "en";
|
||||
// const bookingHashedLink = "";
|
||||
// const bookingRecurringCount = 5;
|
||||
// const currentBookingRecurringIndex = 0;
|
||||
|
||||
// const body = {
|
||||
// start: bookingStart,
|
||||
// end: bookingEnd,
|
||||
// eventTypeId: bookingEventTypeId,
|
||||
// timeZone: bookingTimeZone,
|
||||
// language: bookingLanguage,
|
||||
// metadata: {},
|
||||
// hashedLink: bookingHashedLink,
|
||||
// recurringCount: bookingRecurringCount,
|
||||
// currentRecurringIndex: currentBookingRecurringIndex,
|
||||
// };
|
||||
|
||||
// return request(app.getHttpServer())
|
||||
// .post("/v2/bookings/recurring")
|
||||
// .send(body)
|
||||
// .expect(201)
|
||||
// .then((response) => {
|
||||
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleNewRecurringBooking>>> =
|
||||
// response.body;
|
||||
|
||||
// expect(responseBody.status).toEqual("recurring");
|
||||
// });
|
||||
// });
|
||||
|
||||
// note(Lauris) : found this test broken here - first thing to fix is that the eventTypeId must be team event type, because
|
||||
// instant bookings only work for teams.
|
||||
// it("should create an instant booking", async () => {
|
||||
// const bookingStart = "2040-05-25T09:30:00.000Z";
|
||||
// const bookingEnd = "2040-25T10:30:00.000Z";
|
||||
// const bookingEventTypeId = 7;
|
||||
// const bookingTimeZone = "Europe/London";
|
||||
// const bookingLanguage = "en";
|
||||
// const bookingHashedLink = "";
|
||||
|
||||
// const body = {
|
||||
// start: bookingStart,
|
||||
// end: bookingEnd,
|
||||
// eventTypeId: bookingEventTypeId,
|
||||
// timeZone: bookingTimeZone,
|
||||
// language: bookingLanguage,
|
||||
// metadata: {},
|
||||
// hashedLink: bookingHashedLink,
|
||||
// };
|
||||
|
||||
// return request(app.getHttpServer())
|
||||
// .post("/v2/bookings/instant")
|
||||
// .send(body)
|
||||
// .expect(201)
|
||||
// .then((response) => {
|
||||
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>> = response.body;
|
||||
|
||||
// expect(responseBody.status).toEqual("instant");
|
||||
// });
|
||||
// });
|
||||
|
||||
// cancelling a booking hangs the test for some reason
|
||||
it.skip("should cancel a booking", async () => {
|
||||
const bookingId = createdBooking.id;
|
||||
|
||||
const body = {
|
||||
allRemainingBookings: false,
|
||||
cancellationReason: "Was fighting some unforseen rescheduling demons",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${bookingId}/cancel`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then((response) => {
|
||||
const responseBody: ApiResponse<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> =
|
||||
response.body;
|
||||
|
||||
expect(bookingId).toBeDefined();
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { User } from "@prisma/client";
|
||||
import { CreationSource } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { NextApiRequest } from "next/types";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -383,7 +384,11 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
});
|
||||
Object.assign(clone, { userId, ...oAuthParams, platformBookingLocation });
|
||||
clone.body = { ...clone.body, noEmail: !oAuthParams.arePlatformEmailsEnabled };
|
||||
clone.body = {
|
||||
...clone.body,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -411,6 +416,7 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
platformBookingLocation,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
});
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -126,4 +126,47 @@ export class CreateBookingInput_2024_04_15 {
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
locationUrl?: string;
|
||||
|
||||
// note(rajiv): after going through getUrlSearchParamsToForward.ts we found out
|
||||
// that the below properties were not being included inside of handleNewBooking :- cc @morgan
|
||||
// cal.salesforce.rrSkipToAccountLookupField, cal.rerouting & cal.isTestPreviewLink
|
||||
// hence no input values have been setup for them in CreateBookingInput_2024_04_15
|
||||
@IsArray()
|
||||
@Type(() => Number)
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
routedTeamMemberIds?: number[];
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
routingFormResponseId?: number;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
skipContactOwner?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
_shouldServeCache?: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
_isDryRun?: boolean;
|
||||
|
||||
// reroutingFormResponses is similar to rescheduling which can only be done by the organiser
|
||||
// won't really be necessary here in our usecase though :- cc @Hariom
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@ApiHideProperty()
|
||||
reroutingFormResponses?: Record<
|
||||
string,
|
||||
{
|
||||
value: (string | number | string[]) & (string | number | string[] | undefined);
|
||||
label?: string | undefined;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
+12
-4
@@ -19,6 +19,7 @@ import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.rep
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -72,10 +73,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let apiKeyString: string;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `api-key-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = `api-key-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking: BookingOutput_2024_08_13;
|
||||
let rescheduledBooking: BookingOutput_2024_08_13;
|
||||
@@ -100,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `api-key-bookings-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -110,13 +114,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
apiKeyString = keyString;
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `api-key-bookings-e2e-api-key-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `api-key-bookings-e2e-api-key-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: eventTypeSlug,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
+122
-108
@@ -16,7 +16,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomNumber } from "test/utils/randomNumber";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -26,9 +26,7 @@ import {
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { BookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import { Booking, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
|
||||
const suffix = randomNumber();
|
||||
import { Booking, PlatformOAuthClient, Team, User, EventType } from "@calcom/prisma/client";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Booking fields", () => {
|
||||
@@ -43,7 +41,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = `alice-${suffix}@api.com`;
|
||||
const userEmail = `booking-fields-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let bookingWithSplitName: Booking;
|
||||
@@ -54,7 +52,12 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
let seatedBookingWithSplitName: Booking;
|
||||
|
||||
let eventTypeWithBookingFieldsId: number;
|
||||
let eventTypeId: number;
|
||||
let seatedEvent: EventType;
|
||||
let eventTypeWithBookingFields: EventType;
|
||||
const eventTypeSlug = `booking-fields-2024-08-13-event-type-${randomString()}`;
|
||||
const eventTypeWithBookingFieldsSlug = `booking-fields-2024-08-13-event-type-${randomString()}`;
|
||||
const seatedEventTypeSlug = `booking-fields-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -76,7 +79,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: `booking fields ${suffix}` });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `booking-fields-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -89,109 +94,36 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `booking-fields-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: `normal-booking-${randomNumber()}`, length: 60 },
|
||||
user.id
|
||||
);
|
||||
|
||||
bookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 12, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 13, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: event.id,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: `seated-${randomNumber()}`, length: 60, seatsPerTimeSlot: 3 },
|
||||
user.id
|
||||
);
|
||||
|
||||
seatedBookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 14, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 15, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "seated-booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: seatedEvent.id,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
bookingSeat: {
|
||||
create: {
|
||||
referenceUid: "unique-seat-uid",
|
||||
data: {
|
||||
responses: {
|
||||
email: "oldie@gmail.com",
|
||||
name: {
|
||||
firstName: "Oldie",
|
||||
lastName: "Goldie",
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {},
|
||||
booking: {
|
||||
connect: {
|
||||
uid: "seated-booking",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const eventTypeWithBookingFields = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding with booking fields",
|
||||
slug: `with-custom-booking-fields-${randomNumber()}`,
|
||||
title: `booking-fields-2024-08-13-event-type-${randomString()}`,
|
||||
slug: eventTypeSlug,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
eventTypeId = event.id;
|
||||
|
||||
seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `booking-fields-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventTypeSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 3,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
eventTypeWithBookingFields = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `booking-fields-2024-08-13-event-type-${randomString()}`,
|
||||
slug: eventTypeWithBookingFieldsSlug,
|
||||
length: 60,
|
||||
bookingFields: [
|
||||
{
|
||||
@@ -381,11 +313,93 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeWithBookingFieldsId = eventTypeWithBookingFields.id;
|
||||
|
||||
bookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 12, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 13, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
seatedBookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 14, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 15, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "seated-booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: seatedEvent.id,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
bookingSeat: {
|
||||
create: {
|
||||
referenceUid: "unique-seat-uid",
|
||||
data: {
|
||||
responses: {
|
||||
email: "oldie@gmail.com",
|
||||
name: {
|
||||
firstName: "Oldie",
|
||||
lastName: "Goldie",
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {},
|
||||
booking: {
|
||||
connect: {
|
||||
uid: "seated-booking",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
@@ -457,7 +471,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
it("should not be able to book an event type with custom required booking fields if they are missing in bookingFieldsResponses", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: eventTypeWithBookingFieldsId,
|
||||
eventTypeId: eventTypeWithBookingFields.id,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
@@ -476,7 +490,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
it("should be able to book an event type with custom required booking fields", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: eventTypeWithBookingFieldsId,
|
||||
eventTypeId: eventTypeWithBookingFields.id,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
|
||||
+12
-5
@@ -16,6 +16,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -35,11 +36,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `confirm-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = "peer-coding";
|
||||
const eventTypeSlug = `confirm-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking1: Booking;
|
||||
let createdBooking2: Booking;
|
||||
@@ -64,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -77,13 +80,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-bookings-2024-08-13-${randomString()}-schedule`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: eventTypeSlug, length: 60 },
|
||||
{
|
||||
title: `confirm-bookings-2024-08-13-${randomString()}-event-type`,
|
||||
slug: `confirm-bookings-2024-08-13-${randomString()}-event-type-slug`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
+21
-8
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -92,7 +93,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = `admin-${Math.floor(Math.random() * 1000)}@example.com`;
|
||||
const authEmail = `confirm-emails-2024-08-13-admin-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -114,7 +115,9 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -139,7 +142,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -148,14 +151,19 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
@@ -171,7 +179,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -179,13 +187,18 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
|
||||
+23
-20
@@ -23,6 +23,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -103,7 +104,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = "admin@example.com";
|
||||
const authEmail = "team-emails-2024-08-13-user-admin@example.com";
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -128,7 +129,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({ name: `team-emails-organization-${randomString()}` });
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -152,7 +153,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `team-emails-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -163,7 +164,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "alice@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -172,7 +173,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "bob@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -181,7 +182,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -237,8 +238,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -278,8 +279,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -336,7 +337,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 2",
|
||||
name: `team-emails-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -347,7 +348,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "charlie@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -356,7 +357,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "dean@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -365,7 +366,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -421,8 +422,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -462,8 +463,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -965,17 +966,19 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await userRepositoryFixture.deleteByEmail(authEmail);
|
||||
await userRepositoryFixture.deleteByEmail(emailsEnabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member2.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsEnabledSetup.member1.id,
|
||||
emailsEnabledSetup.member1.email
|
||||
);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsDisabledSetup.member1.id,
|
||||
emailsDisabledSetup.member1.email
|
||||
emailsDisabledSetup.member2.email
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
+22
-11
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -101,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `user-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -116,7 +119,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -125,22 +128,26 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
@@ -160,7 +167,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -168,21 +175,25 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
|
||||
+12
-9
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -45,8 +46,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const teamUserEmail = "orgUser1team1@api.com";
|
||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
||||
const teamUserEmail = `reassign-bookings-2024-08-13-user1-${randomString()}@api.com`;
|
||||
const teamUserEmail2 = `reassign-bookings-2024-08-13-user2-${randomString()}@api.com`;
|
||||
let teamUser1: User;
|
||||
let teamUser2: User;
|
||||
|
||||
@@ -78,11 +79,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization team bookings" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `reassign-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `reassign-bookings-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -95,17 +98,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamUser1 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail,
|
||||
locale: "it",
|
||||
name: "orgUser1team1",
|
||||
name: `reassign-bookings-2024-08-13-user1-${randomString()}`,
|
||||
});
|
||||
|
||||
teamUser2 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail2,
|
||||
locale: "it",
|
||||
name: "orgUser2team1",
|
||||
name: `reassign-bookings-2024-08-13-user2-${randomString()}`,
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `reassign-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -164,8 +167,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
users: {
|
||||
connect: [{ id: teamUser1.id }, { id: teamUser2.id }],
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
|
||||
+26
-15
@@ -17,6 +17,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -41,11 +42,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 3;
|
||||
let recurringEventTypeId: number;
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
let createdRecurringBooking: RecurringBookingOutput_2024_08_13[];
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -67,7 +70,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -80,7 +85,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -89,8 +94,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -344,7 +349,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -380,7 +385,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -393,17 +400,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -551,7 +559,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -587,7 +595,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -600,17 +610,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
|
||||
+45
-44
@@ -19,6 +19,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -51,22 +52,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_seated@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper Seated";
|
||||
const emailAttendeeTwo = "mr_proper_friend_seated@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Friend Seated";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -88,7 +90,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -101,15 +105,15 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
@@ -123,8 +127,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
@@ -200,7 +204,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
@@ -272,7 +276,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(2);
|
||||
// note(Lauris): first attendee is from previous test request
|
||||
@@ -366,7 +370,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -401,7 +405,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -575,7 +579,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
const attendee = createdSeatedBooking.attendees.find((a) => a.seatUid === body.seatUid);
|
||||
@@ -629,7 +633,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
@@ -681,22 +685,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_first@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper First";
|
||||
const emailAttendeeTwo = "mr_proper_second@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Second";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -718,7 +723,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -731,20 +738,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
seatsPerTimeSlot: 3,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -753,14 +757,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 3,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -836,7 +837,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -871,7 +872,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -949,7 +950,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(2);
|
||||
const firstAttendee = firstBooking.attendees.find(
|
||||
@@ -1003,7 +1004,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(2);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
|
||||
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -52,13 +53,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const teamUserEmail = "orgUser1team1@api.com";
|
||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
||||
const teamUserEmail = `team-bookings-user1-${randomString()}@api.com`;
|
||||
const teamUserEmail2 = `team-bookings-user2-${randomString()}@api.com`;
|
||||
let teamUser: User;
|
||||
let teamUser2: User;
|
||||
|
||||
let team1EventTypeId: number;
|
||||
let team2EventTypeId: number;
|
||||
let phoneOnlyEventTypeId: number;
|
||||
|
||||
const team1EventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||
const team2EventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||
const phoneOnlyEventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -84,11 +90,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization team bookings" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `team-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
team1 = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `team-bookings-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -99,7 +107,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
team2 = await teamRepositoryFixture.create({
|
||||
name: "team 2",
|
||||
name: `team-bookings-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -132,7 +140,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -195,8 +203,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
team: {
|
||||
connect: { id: team1.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: team1EventTypeSlug,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -205,13 +213,90 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
team1EventTypeId = team1EventType.id;
|
||||
|
||||
const phoneOnlyEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "ROUND_ROBIN",
|
||||
team: {
|
||||
connect: { id: team1.id },
|
||||
},
|
||||
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: phoneOnlyEventTypeSlug,
|
||||
length: 15,
|
||||
assignAllTeamMembers: false,
|
||||
hosts: {
|
||||
connectOrCreate: [
|
||||
{
|
||||
where: {
|
||||
userId_eventTypeId: {
|
||||
userId: teamUser.id,
|
||||
eventTypeId: team1EventTypeId,
|
||||
},
|
||||
},
|
||||
create: {
|
||||
userId: teamUser.id,
|
||||
isFixed: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
label: "your name",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
variant: "fullName",
|
||||
editable: "system",
|
||||
required: true,
|
||||
defaultLabel: "your_name",
|
||||
variantsConfig: {
|
||||
variants: {
|
||||
fullName: {
|
||||
fields: [{ name: "fullName", type: "text", label: "your name", required: true }],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
label: "your email",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system",
|
||||
required: false,
|
||||
defaultLabel: "email_address",
|
||||
},
|
||||
{
|
||||
name: "attendeePhoneNumber",
|
||||
type: "phone",
|
||||
label: "phone_number",
|
||||
sources: [{ id: "user", type: "user", label: "User", fieldRequired: true }],
|
||||
editable: "user",
|
||||
required: true,
|
||||
placeholder: "",
|
||||
},
|
||||
{
|
||||
name: "rescheduleReason",
|
||||
type: "textarea",
|
||||
views: [{ id: "reschedule", label: "Reschedule View" }],
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "reason_for_reschedule",
|
||||
defaultPlaceholder: "reschedule_placeholder",
|
||||
},
|
||||
],
|
||||
locations: [],
|
||||
});
|
||||
|
||||
phoneOnlyEventTypeId = phoneOnlyEventType.id;
|
||||
|
||||
const team2EventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "COLLECTIVE",
|
||||
team: {
|
||||
connect: { id: team2.id },
|
||||
},
|
||||
title: "Collective Event Type 2",
|
||||
slug: "collective-event-type-2",
|
||||
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: team2EventTypeSlug,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -322,6 +407,60 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a phone based booking", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(),
|
||||
eventTypeId: phoneOnlyEventTypeId,
|
||||
attendee: {
|
||||
name: "alice",
|
||||
phoneNumber: "+919876543210",
|
||||
timeZone: "Europe/Madrid",
|
||||
language: "es",
|
||||
},
|
||||
meetingUrl: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts.length).toEqual(1);
|
||||
expect(data.hosts[0].id).toEqual(teamUser.id);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 15, 15, 0)).toISOString());
|
||||
expect(data.duration).toEqual(15);
|
||||
expect(data.eventTypeId).toEqual(phoneOnlyEventTypeId);
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: "919876543210@sms.cal.com",
|
||||
phoneNumber: body.attendee.phoneNumber,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
});
|
||||
expect(data.meetingUrl).toEqual(body.meetingUrl);
|
||||
expect(data.absentHost).toEqual(false);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a team 2 booking", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
|
||||
@@ -398,7 +537,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
| RecurringBookingOutput_2024_08_13
|
||||
| GetSeatedBookingOutput_2024_08_13
|
||||
)[] = responseBody.data;
|
||||
expect(data.length).toEqual(1);
|
||||
expect(data.length).toEqual(2);
|
||||
expect(data[0].eventTypeId).toEqual(team1EventTypeId);
|
||||
});
|
||||
});
|
||||
@@ -436,7 +575,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
| RecurringBookingOutput_2024_08_13
|
||||
| GetSeatedBookingOutput_2024_08_13
|
||||
)[] = responseBody.data;
|
||||
expect(data.length).toEqual(2);
|
||||
expect(data.length).toEqual(3);
|
||||
expect(data.find((booking) => booking.eventTypeId === team1EventTypeId)).toBeDefined();
|
||||
expect(data.find((booking) => booking.eventTypeId === team2EventTypeId)).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
@@ -58,12 +59,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `user-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = "peer-coding";
|
||||
const eventTypeSlug = `user-bookings-event-type-${randomString()}`;
|
||||
let recurringEventTypeId: number;
|
||||
const recurringEventTypeSlug = `user-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking: BookingOutput_2024_08_13;
|
||||
let rescheduledBooking: BookingOutput_2024_08_13;
|
||||
@@ -91,12 +93,14 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `user-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
username: userEmail,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClient.id,
|
||||
@@ -105,13 +109,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: eventTypeSlug, length: 60 },
|
||||
{
|
||||
title: `user-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: eventTypeSlug,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
@@ -120,7 +129,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
slug: recurringEventTypeSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
@@ -332,6 +341,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
metadata: {
|
||||
userId: "100",
|
||||
},
|
||||
guests: ["bob@gmail.com"],
|
||||
};
|
||||
|
||||
const beforeCreate = new Date();
|
||||
@@ -353,6 +363,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString());
|
||||
@@ -376,7 +387,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
...body.bookingFieldsResponses,
|
||||
guests: body.guests,
|
||||
});
|
||||
expect(data.guests).toEqual(body.guests);
|
||||
|
||||
// Check createdAt date is between the time of the request and after the request
|
||||
const createdAtDate = new Date(data.createdAt);
|
||||
@@ -515,6 +528,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.uid).toEqual(createdBooking.uid);
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(createdBooking.status);
|
||||
expect(data.start).toEqual(createdBooking.start);
|
||||
expect(data.end).toEqual(createdBooking.end);
|
||||
@@ -549,6 +563,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toEqual(createdRecurringBooking[0].id);
|
||||
expect(data.uid).toEqual(createdRecurringBooking[0].uid);
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(createdRecurringBooking[0].status);
|
||||
expect(data.start).toEqual(createdRecurringBooking[0].start);
|
||||
expect(data.end).toEqual(createdRecurringBooking[0].end);
|
||||
@@ -1018,6 +1034,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(createdBooking.status);
|
||||
expect(data.duration).toEqual(createdBooking.duration);
|
||||
expect(data.eventTypeId).toEqual(createdBooking.eventTypeId);
|
||||
@@ -1075,6 +1093,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(createdRecurringBooking[0].status);
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2035, 0, 9, 15, 0, 0)).toISOString());
|
||||
@@ -1155,6 +1175,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toEqual(booking.id);
|
||||
expect(data.uid).toEqual(booking.uid);
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(booking.status);
|
||||
expect(data.start).toEqual(booking.start);
|
||||
expect(data.end).toEqual(booking.end);
|
||||
@@ -1187,6 +1209,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toEqual(booking.id);
|
||||
expect(data.uid).toEqual(booking.uid);
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual(booking.status);
|
||||
expect(data.start).toEqual(booking.start);
|
||||
expect(data.end).toEqual(booking.end);
|
||||
@@ -1225,6 +1249,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts[0].id).toEqual(user.id);
|
||||
expect(data.hosts[0].username).toEqual(user.username);
|
||||
expect(data.hosts[0].email).toEqual(user.email);
|
||||
expect(data.status).toEqual("cancelled");
|
||||
expect(data.cancellationReason).toEqual(body.cancellationReason);
|
||||
expect(data.start).toEqual(rescheduledBooking.start);
|
||||
|
||||
+15
-7
@@ -17,6 +17,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -36,12 +37,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `variable-length-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let variableLengthEventType: EventType;
|
||||
const variableLengthEventTypeSlug = "variable-length-event";
|
||||
const VARIABLE_LENGTH_EVENT_TYPE_SLUG = `variable-length-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
let normalEventType: EventType;
|
||||
const NORMAL_EVENT_TYPE_SLUG = `variable-length-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -63,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `variable-length-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -76,7 +80,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `variable-length-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -84,8 +88,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
variableLengthEventType = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "variable length event",
|
||||
slug: variableLengthEventTypeSlug,
|
||||
title: `variable-length-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: VARIABLE_LENGTH_EVENT_TYPE_SLUG,
|
||||
length: 15,
|
||||
metadata: { multipleDuration: [15, 30, 60] },
|
||||
},
|
||||
@@ -93,7 +97,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
);
|
||||
|
||||
normalEventType = await eventTypesRepositoryFixture.create(
|
||||
{ title: "normal event", slug: "normal-event", length: 15 },
|
||||
{
|
||||
title: `variable-length-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: NORMAL_EVENT_TYPE_SLUG,
|
||||
length: 15,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Request } from "express";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
handleNewBooking,
|
||||
handleNewRecurringBooking,
|
||||
getAllUserBookings,
|
||||
handleInstantMeeting,
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
handleMarkNoShow,
|
||||
confirmBookingHandler,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { handleNewBooking } from "@calcom/platform-libraries";
|
||||
import {
|
||||
CreateBookingInput_2024_08_13,
|
||||
CreateBookingInput,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.se
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { CreationSource } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { DateTime } from "luxon";
|
||||
import { NextApiRequest } from "next/types";
|
||||
@@ -97,10 +98,14 @@ export class InputBookingsService_2024_08_13 {
|
||||
|
||||
if (oAuthClientParams) {
|
||||
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: !oAuthClientParams.arePlatformEmailsEnabled };
|
||||
newRequest.body = {
|
||||
...bodyTransformed,
|
||||
noEmail: !oAuthClientParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
} else {
|
||||
Object.assign(newRequest, { userId, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false };
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false, creationSource: CreationSource.API_V2 };
|
||||
}
|
||||
|
||||
return newRequest as unknown as BookingRequest;
|
||||
@@ -146,6 +151,8 @@ export class InputBookingsService_2024_08_13 {
|
||||
);
|
||||
const endTime = startTime.plus({ minutes: lengthInMinutes });
|
||||
|
||||
const guests = inputBooking.guests;
|
||||
|
||||
return {
|
||||
start: startTime.toISO(),
|
||||
end: endTime.toISO(),
|
||||
@@ -154,15 +161,22 @@ export class InputBookingsService_2024_08_13 {
|
||||
language: inputBooking.attendee.language || "en",
|
||||
metadata: inputBooking.metadata || {},
|
||||
hasHashedBookingLink: false,
|
||||
guests: inputBooking.guests,
|
||||
guests,
|
||||
// note(Lauris): responses with name and email are required by the handleNewBooking
|
||||
responses: inputBooking.bookingFieldsResponses
|
||||
? {
|
||||
...inputBooking.bookingFieldsResponses,
|
||||
name: inputBooking.attendee.name,
|
||||
email: inputBooking.attendee.email,
|
||||
email: inputBooking.attendee.email ?? "",
|
||||
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
|
||||
guests,
|
||||
}
|
||||
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email },
|
||||
: {
|
||||
name: inputBooking.attendee.name,
|
||||
email: inputBooking.attendee.email ?? "",
|
||||
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
|
||||
guests,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -211,6 +225,7 @@ export class InputBookingsService_2024_08_13 {
|
||||
|
||||
newRequest.body = bodyTransformed.map((event) => ({
|
||||
...event,
|
||||
creationSource: CreationSource.API_V2,
|
||||
}));
|
||||
|
||||
return newRequest as unknown as BookingRequest;
|
||||
@@ -246,6 +261,8 @@ export class InputBookingsService_2024_08_13 {
|
||||
inputBooking.attendee.timeZone
|
||||
);
|
||||
|
||||
const guests = inputBooking.guests;
|
||||
|
||||
for (let i = 0; i < repeatsTimes; i++) {
|
||||
const endTime = startTime.plus({ minutes: eventType.length });
|
||||
|
||||
@@ -258,15 +275,16 @@ export class InputBookingsService_2024_08_13 {
|
||||
language: inputBooking.attendee.language || "en",
|
||||
metadata: inputBooking.metadata || {},
|
||||
hasHashedBookingLink: false,
|
||||
guests: inputBooking.guests,
|
||||
guests,
|
||||
// note(Lauris): responses with name and email are required by the handleNewBooking
|
||||
responses: inputBooking.bookingFieldsResponses
|
||||
? {
|
||||
...inputBooking.bookingFieldsResponses,
|
||||
name: inputBooking.attendee.name,
|
||||
email: inputBooking.attendee.email,
|
||||
guests,
|
||||
}
|
||||
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email },
|
||||
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email, guests },
|
||||
schedulingType: eventType.schedulingType,
|
||||
});
|
||||
|
||||
@@ -305,10 +323,14 @@ export class InputBookingsService_2024_08_13 {
|
||||
const location = await this.getRescheduleBookingLocation(bookingUid);
|
||||
if (oAuthClientParams) {
|
||||
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: !oAuthClientParams.arePlatformEmailsEnabled };
|
||||
newRequest.body = {
|
||||
...bodyTransformed,
|
||||
noEmail: !oAuthClientParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
} else {
|
||||
Object.assign(newRequest, { userId, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false };
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false, creationSource: CreationSource.API_V2 };
|
||||
}
|
||||
|
||||
return newRequest as unknown as BookingRequest;
|
||||
|
||||
@@ -72,6 +72,7 @@ type DatabaseBooking = Booking & {
|
||||
email: string;
|
||||
timeZone: string;
|
||||
locale: string | null;
|
||||
phoneNumber?: string | null;
|
||||
noShow: boolean | null;
|
||||
bookingSeat?: BookingSeat | null;
|
||||
}[];
|
||||
@@ -121,6 +122,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
phoneNumber: attendee.phoneNumber ?? undefined,
|
||||
})),
|
||||
guests: bookingResponses.guests,
|
||||
location,
|
||||
|
||||
@@ -20,6 +20,7 @@ import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.reposit
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-service-mock";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -71,9 +72,12 @@ describe("Platform Calendars Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
|
||||
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({ name: `calendars-organization-${randomString()}` });
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser("office365-connect@gmail.com", oAuthClient.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser(
|
||||
`calendars-user-${randomString()}@api.com`,
|
||||
oAuthClient.id
|
||||
);
|
||||
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id);
|
||||
accessTokenSecret = tokens.accessToken;
|
||||
refreshTokenSecret = tokens.refreshToken;
|
||||
|
||||
+7
-4
@@ -22,6 +22,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
@@ -77,8 +78,8 @@ describe("Event types Endpoints", () => {
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
|
||||
const userEmail = "event-types-test-e2e@api.com";
|
||||
const name = "bob-the-builder";
|
||||
const userEmail = `event-types-2024-04-15-user-${randomString()}@api.com`;
|
||||
const name = `event-types-2024-04-15-user-${randomString()}`;
|
||||
const username = name;
|
||||
let eventType: EventType;
|
||||
let user: User;
|
||||
@@ -105,7 +106,9 @@ describe("Event types Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `event-types-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -139,7 +142,7 @@ describe("Event types Endpoints", () => {
|
||||
it("should create an event type", async () => {
|
||||
const body: CreateEventTypeInput_2024_04_15 = {
|
||||
title: "Test Event Type",
|
||||
slug: "test-event-type",
|
||||
slug: `event-types-event-type-${randomString()}`,
|
||||
description: "A description of the test event type.",
|
||||
length: 60,
|
||||
hidden: false,
|
||||
|
||||
+4
-4
@@ -399,8 +399,8 @@ class PublicEventTypeOutput {
|
||||
workflows!: any[];
|
||||
|
||||
@IsArray()
|
||||
@ApiProperty()
|
||||
hosts!: any[];
|
||||
@ApiPropertyOptional()
|
||||
hosts?: any[];
|
||||
|
||||
@ValidateNested()
|
||||
@Type(() => Owner)
|
||||
@@ -427,8 +427,8 @@ class PublicEventTypeOutput {
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => User)
|
||||
@ApiProperty({ type: [User] })
|
||||
users!: User[];
|
||||
@ApiPropertyOptional({ type: [User] })
|
||||
users?: User[];
|
||||
|
||||
@IsObject()
|
||||
@ApiProperty({ type: Object })
|
||||
|
||||
+59
-37
@@ -19,6 +19,7 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
@@ -86,9 +87,9 @@ describe("Event types Endpoints", () => {
|
||||
let schedulesRepostoryFixture: SchedulesRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
const userEmail = "event-types-test-e2e@api.com";
|
||||
const falseTestEmail = "false-event-types@api.com";
|
||||
const name = "bob-the-builder";
|
||||
const userEmail = `event-types-2024-06-14-user-${randomString()}@api.com`;
|
||||
const falseTestEmail = `event-types-2024-06-14-false-user-${randomString()}@api.com`;
|
||||
const name = `event-types-2024-06-14-user-${randomString()}`;
|
||||
const username = name;
|
||||
let eventType: EventTypeOutput_2024_06_14;
|
||||
let user: User;
|
||||
@@ -123,7 +124,6 @@ describe("Event types Endpoints", () => {
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldTitle = {
|
||||
@@ -187,8 +187,8 @@ describe("Event types Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: "organization",
|
||||
slug: "event-type-2024-06-14-org-slug",
|
||||
name: `event-types-2024-06-14-organization-${randomString()}`,
|
||||
slug: `event-type-2024-06-14-org-slug-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -198,9 +198,9 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
orgUser = await userRepositoryFixture.create({
|
||||
email: "event-types-2024-06-14-org-user@example.com",
|
||||
name: "event-types-2024-06-14-org-user",
|
||||
username: "event-types-2024-06-14-org-user",
|
||||
email: `event-types-2024-06-14-org-user-${randomString()}@example.com`,
|
||||
name: `event-types-2024-06-14-org-user-${randomString()}`,
|
||||
username: `event-types-2024-06-14-org-user-${randomString()}`,
|
||||
});
|
||||
|
||||
profileRepositoryFixture.create({
|
||||
@@ -219,17 +219,32 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
orgUserEventType1 = await eventTypesRepositoryFixture.create(
|
||||
{ title: "orgUserEventType1", slug: "org-event-type-1", length: 60, locations: [] },
|
||||
{
|
||||
title: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
slug: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
locations: [],
|
||||
},
|
||||
orgUser.id
|
||||
);
|
||||
|
||||
orgUserEventType2 = await eventTypesRepositoryFixture.create(
|
||||
{ title: "orgUserEventType2", slug: "org-event-type-2", length: 60, locations: [] },
|
||||
{
|
||||
title: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
slug: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
locations: [],
|
||||
},
|
||||
orgUser.id
|
||||
);
|
||||
|
||||
orgUserEventType3 = await eventTypesRepositoryFixture.create(
|
||||
{ title: "orgUserEventType3", slug: "org-event-type-3", length: 60, locations: [] },
|
||||
{
|
||||
title: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
slug: `event-types-2024-06-14-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
locations: [],
|
||||
},
|
||||
orgUser.id
|
||||
);
|
||||
|
||||
@@ -242,25 +257,25 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
falseTestUser = await userRepositoryFixture.create({
|
||||
email: falseTestEmail,
|
||||
name: "false-test",
|
||||
name: `event-types-2024-06-14-false-test-user-${randomString()}`,
|
||||
username: falseTestEmail,
|
||||
});
|
||||
|
||||
firstSchedule = await schedulesRepostoryFixture.create({
|
||||
userId: user.id,
|
||||
name: "work",
|
||||
name: `event-types-2024-06-14-schedule-work-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
});
|
||||
|
||||
secondSchedule = await schedulesRepostoryFixture.create({
|
||||
userId: user.id,
|
||||
name: "chill",
|
||||
name: `event-types-2024-06-14-schedule-chill-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
});
|
||||
|
||||
falseTestSchedule = await schedulesRepostoryFixture.create({
|
||||
userId: falseTestUser.id,
|
||||
name: "work",
|
||||
name: `event-types-2024-06-14-schedule-work-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
});
|
||||
|
||||
@@ -452,9 +467,6 @@ describe("Event types Endpoints", () => {
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
@@ -466,7 +478,10 @@ describe("Event types Endpoints", () => {
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldNotes },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
];
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -942,9 +957,6 @@ describe("Event types Endpoints", () => {
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
@@ -956,7 +968,10 @@ describe("Event types Endpoints", () => {
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldNotes, ...notesBookingField },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
];
|
||||
|
||||
expect(updatedEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -1163,10 +1178,7 @@ describe("Event types Endpoints", () => {
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
label: "",
|
||||
placeholder: "",
|
||||
},
|
||||
{ isDefault: true, required: true, slug: "title", type: "text", disableOnPrefill: false, hidden: true },
|
||||
{
|
||||
@@ -1185,7 +1197,14 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{ isDefault: true, type: "phone", slug: "attendeePhoneNumber", required: false, hidden: true },
|
||||
{
|
||||
disableOnPrefill: false,
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
@@ -1218,7 +1237,9 @@ describe("Event types Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `event-types-2024-06-14-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -1610,7 +1631,6 @@ describe("Event types Endpoints", () => {
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
@@ -1621,6 +1641,14 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
@@ -1637,14 +1665,6 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1706,7 +1726,9 @@ describe("Event types Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `event-types-2024-06-14-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
|
||||
+2
-13
@@ -128,6 +128,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
const bookingFields = databaseEventType.bookingFields
|
||||
? this.transformBookingFields(databaseEventType.bookingFields)
|
||||
: this.getDefaultBookingFields(isOrgTeamEvent);
|
||||
|
||||
const recurrence = this.transformRecurringEvent(databaseEventType.recurringEvent);
|
||||
const metadata = this.transformMetadata(databaseEventType.metadata) || {};
|
||||
const users = this.transformUsers(databaseEventType.users || []);
|
||||
@@ -242,19 +243,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
}
|
||||
}
|
||||
|
||||
const fields = [...transformBookingFieldsInternalToApi(knownBookingFields), ...unknownBookingFields];
|
||||
|
||||
// ensure additional notes are always at the end
|
||||
return fields.sort((a, b) => {
|
||||
if (!a?.slug || !b?.slug) return 0;
|
||||
if (a.slug === "notes" && b.slug !== "notes") {
|
||||
return 1;
|
||||
}
|
||||
if (b.slug === "notes" && a.slug !== "notes") {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return [...transformBookingFieldsInternalToApi(knownBookingFields), ...unknownBookingFields];
|
||||
}
|
||||
|
||||
getDefaultBookingFields(isOrgTeamEvent: boolean) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -29,7 +30,7 @@ describe("Me Endpoints", () => {
|
||||
let schedulesRepositoryFixture: SchedulesRepositoryFixture;
|
||||
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
const userEmail = "me-controller-e2e@api.com";
|
||||
const userEmail = `me-controller-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
|
||||
@@ -58,7 +59,7 @@ describe("Me Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `me-controller-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
isPlatform: true,
|
||||
});
|
||||
|
||||
+6
-7
@@ -16,6 +16,7 @@ import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_04_15 } from "@calcom/platform-constants";
|
||||
@@ -28,10 +29,11 @@ describe("Schedules Endpoints", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let scheduleRepositoryFixture: SchedulesRepositoryFixture;
|
||||
|
||||
const userEmail = "schedules-controller-e2e@api.com";
|
||||
const userEmail = `schedules-2024-04-15-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let createdSchedule: CreateScheduleOutput_2024_04_15["data"];
|
||||
const scheduleName = `schedules-2024-04-15-schedule-${randomString()}`;
|
||||
const defaultAvailabilityDays = [1, 2, 3, 4, 5];
|
||||
const defaultAvailabilityStartTime = "1970-01-01T09:00:00.000Z";
|
||||
const defaultAvailabilityEndTime = "1970-01-01T17:00:00.000Z";
|
||||
@@ -67,7 +69,6 @@ describe("Schedules Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should not create an invalid schedule", async () => {
|
||||
const scheduleName = "schedule-name";
|
||||
const scheduleTimeZone = "Europe/Rome";
|
||||
const isDefault = true;
|
||||
|
||||
@@ -98,13 +99,11 @@ describe("Schedules Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create a default schedule", async () => {
|
||||
const scheduleName = "schedule-name";
|
||||
const scheduleTimeZone = "Europe/Rome";
|
||||
const isDefault = true;
|
||||
|
||||
const body: CreateScheduleInput_2024_04_15 = {
|
||||
name: scheduleName,
|
||||
timeZone: scheduleTimeZone,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault,
|
||||
};
|
||||
|
||||
@@ -118,7 +117,7 @@ describe("Schedules Endpoints", () => {
|
||||
expect(responseData.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseData.data).toBeDefined();
|
||||
expect(responseData.data.isDefault).toEqual(isDefault);
|
||||
expect(responseData.data.timeZone).toEqual(scheduleTimeZone);
|
||||
expect(responseData.data.timeZone).toEqual("Europe/Rome");
|
||||
expect(responseData.data.name).toEqual(scheduleName);
|
||||
|
||||
const schedule = responseData.data.schedule;
|
||||
@@ -177,7 +176,7 @@ describe("Schedules Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update schedule name", async () => {
|
||||
const newScheduleName = "new-schedule-name";
|
||||
const newScheduleName = `schedules-2024-04-15-schedule-${randomString()}`;
|
||||
|
||||
const body: UpdateScheduleInput_2024_04_15 = {
|
||||
name: newScheduleName,
|
||||
|
||||
+2
-2
@@ -32,11 +32,11 @@ describe("Schedules Endpoints", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let scheduleRepositoryFixture: SchedulesRepositoryFixture;
|
||||
|
||||
const userEmail = "schedules-controller-e2e@api.com";
|
||||
const userEmail = `schedules-2024-06-11-user@api.com`;
|
||||
let user: User;
|
||||
|
||||
const createScheduleInput: CreateScheduleInput_2024_06_11 = {
|
||||
name: "work",
|
||||
name: `schedules-2024-06-11-work`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
|
||||
@@ -23,6 +23,7 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { MockedRedisService } from "test/mocks/mock-redis-service";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
|
||||
@@ -39,9 +40,9 @@ describe("ApiAuthStrategy", () => {
|
||||
let oAuthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const validApiKeyEmail = "api-key-user-email@example.com";
|
||||
const validAccessTokenEmail = "access-token-user-email@example.com";
|
||||
const validOAuthEmail = "oauth-user@example.com";
|
||||
const validApiKeyEmail = `api-auth-api-key-user-${randomString()}@api.com`;
|
||||
const validAccessTokenEmail = `api-auth-access-token-user-${randomString()}@api.com`;
|
||||
const validOAuthEmail = `api-auth-oauth-user-${randomString()}@api.com`;
|
||||
|
||||
let validApiKeyUser: User;
|
||||
let validAccessTokenUser: User;
|
||||
@@ -83,7 +84,7 @@ describe("ApiAuthStrategy", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(module);
|
||||
oAuthClientRepositoryFixture = new OAuthClientRepositoryFixture(module);
|
||||
profilesRepositoryFixture = new ProfileRepositoryFixture(module);
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({ name: `api-auth-organization-${randomString()}` });
|
||||
validApiKeyUser = await userRepositoryFixture.create({
|
||||
email: validApiKeyEmail,
|
||||
});
|
||||
|
||||
@@ -16,13 +16,14 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { Team, PlatformOAuthClient, PlatformBilling } from "@calcom/prisma/client";
|
||||
|
||||
describe("Platform Billing Controller (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "platform-billing-controller-e2e@api.com";
|
||||
const userEmail = `billing-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let billing: PlatformBilling;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
@@ -47,7 +48,9 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
platformBillingRepositoryFixture = new PlatformBillingRepositoryFixture(moduleRef);
|
||||
organization = await organizationsRepositoryFixture.create({ name: "platform billing" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `billing-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
@@ -30,7 +31,7 @@ describe("Conferencing Endpoints", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let credentialsRepositoryFixture: CredentialsRepositoryFixture;
|
||||
|
||||
const userEmail = "conferencing-controller-user-e2e@api.com";
|
||||
const userEmail = `conferencing-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
+8
-2
@@ -17,6 +17,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -55,9 +56,14 @@ describe("Platform Destination Calendar Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
|
||||
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `destination-calendars-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser("office365-connect@gmail.com", oAuthClient.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser(
|
||||
`destination-calendars-user-${randomString()}@api.com`,
|
||||
oAuthClient.id
|
||||
);
|
||||
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id);
|
||||
accessTokenSecret = tokens.accessToken;
|
||||
refreshTokenSecret = tokens.refreshToken;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BillingModule } from "@/modules/billing/billing.module";
|
||||
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 { RouterModule } from "@/modules/router/router.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { TimezoneModule } from "@/modules/timezones/timezones.module";
|
||||
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
||||
@@ -24,6 +25,7 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
|
||||
AtomsModule,
|
||||
StripeModule,
|
||||
ConferencingModule,
|
||||
RouterModule,
|
||||
],
|
||||
})
|
||||
export class EndpointsModule implements NestModule {
|
||||
|
||||
+7
-6
@@ -17,13 +17,14 @@ import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { EventType, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("EventTypes WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "event-types-webhook-controller-e2e@api.com";
|
||||
const userEmail = `event-types-webhooks-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let otherUser: UserWithProfile;
|
||||
let eventType: EventType;
|
||||
@@ -55,14 +56,14 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
});
|
||||
|
||||
otherUser = await userRepositoryFixture.create({
|
||||
email: "other-user-webhook-controller@api.com",
|
||||
username: "other-user-webhook-controller@api.com",
|
||||
email: `event-types-webhooks-other-user-${randomString()}@api.com`,
|
||||
username: `event-types-webhooks-other-user-${randomString()}@api.com`,
|
||||
});
|
||||
|
||||
eventType = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Event Type 1",
|
||||
slug: "webhook-event-type-1",
|
||||
slug: `event-types-webhooks-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
@@ -71,7 +72,7 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
eventType2 = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Event Type 2",
|
||||
slug: "webhook-event-type-2",
|
||||
slug: `event-types-webhooks-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
@@ -80,7 +81,7 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
otherEventType = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Other Event Type ",
|
||||
slug: "other-webhook-event-type",
|
||||
slug: `event-types-webhooks-other-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
otherUser.id
|
||||
|
||||
+16
-9
@@ -22,6 +22,7 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.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";
|
||||
import { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
@@ -84,10 +85,10 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
|
||||
let postResponseData: CreateManagedUserOutput["data"];
|
||||
|
||||
const platformAdminEmail = "platform-sensei@mail.com";
|
||||
const platformAdminEmail = `oauth-client-users-admin-${randomString()}@api.com`;
|
||||
let platformAdmin: User;
|
||||
|
||||
const userEmail = "oauth-client-user@gmail.com";
|
||||
const userEmail = `oauth-client-users-user-${randomString()}@api.com`;
|
||||
const userTimeZone = "Europe/Rome";
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -110,7 +111,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
|
||||
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: "organization",
|
||||
name: `oauth-client-users-organization-${randomString()}`,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
});
|
||||
@@ -220,6 +221,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
await userConnectedToOAuth(responseBody.data.user.email);
|
||||
await userHasDefaultEventTypes(responseBody.data.user.id);
|
||||
await userHasDefaultSchedule(responseBody.data.user.id, responseBody.data.user.defaultScheduleId);
|
||||
await userHasOnlyOneSchedule(responseBody.data.user.id);
|
||||
});
|
||||
|
||||
async function userConnectedToOAuth(userEmail: string) {
|
||||
@@ -260,6 +262,11 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
expect(schedule?.userId).toEqual(userId);
|
||||
}
|
||||
|
||||
async function userHasOnlyOneSchedule(userId: number) {
|
||||
const schedules = await schedulesRepositoryFixture.getByUserId(userId);
|
||||
expect(schedules?.length).toEqual(1);
|
||||
}
|
||||
|
||||
it(`should fail /POST using already used managed user email`, async () => {
|
||||
const requestBody: CreateManagedUserInput = {
|
||||
email: userEmail,
|
||||
@@ -386,7 +393,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let postResponseData: CreateManagedUserOutput["data"];
|
||||
|
||||
const userEmail = "oauth-client-users-user@gmail.com";
|
||||
const userEmail = `oauth-client-users-user-${randomString()}@api.com`;
|
||||
const userTimeZone = "Europe/Rome";
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -405,20 +412,20 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: "Testy Organization",
|
||||
isOrganization: true,
|
||||
name: `oauth-client-users-organization-${randomString()}`,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
owner = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
email: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
username: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
organization: { connect: { id: organization.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${owner.id}`,
|
||||
username: userEmail,
|
||||
username: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
organization: {
|
||||
connect: {
|
||||
id: organization.id,
|
||||
|
||||
+5
-4
@@ -20,14 +20,15 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { PlatformOAuthClient, Team, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("OAuth client WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "oauth-client-webhook-controller-e2e@api.com";
|
||||
const otherUserEmail = "other-oauth-client-webhook-controller-e2e@api.com";
|
||||
const userEmail = `oauth-client-webhooks-user-${randomString()}@api.com`;
|
||||
const otherUserEmail = `oauth-client-webhooks-other-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let otherUser: UserWithProfile;
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
@@ -69,7 +70,7 @@ describe("OAuth client WebhooksController (e2e)", () => {
|
||||
});
|
||||
|
||||
org = await orgRepositoryFixture.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-client-webhooks-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -80,7 +81,7 @@ describe("OAuth client WebhooksController (e2e)", () => {
|
||||
isPlatform: true,
|
||||
});
|
||||
otherOrg = await orgRepositoryFixture.create({
|
||||
name: "otherOrg",
|
||||
name: `oauth-client-webhooks-other-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
|
||||
+9
-8
@@ -18,6 +18,7 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { NextAuthMockStrategy } from "test/mocks/next-auth-mock.strategy";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -68,7 +69,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let app: INestApplication;
|
||||
const userEmail = "oauth-clients-test-e2e@api.com";
|
||||
const userEmail = `oauth-clients-user-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withNextAuth(
|
||||
@@ -89,7 +90,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
org = await teamFixtures.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-clients-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -126,7 +127,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let app: INestApplication;
|
||||
const userEmail = "test-e2e@api.com";
|
||||
const userEmail = `oauth-clients-user-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withNextAuth(
|
||||
@@ -147,7 +148,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
org = await teamFixtures.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-clients-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -211,7 +212,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
describe("User is part of an organization as Admin", () => {
|
||||
let membership: Membership;
|
||||
let client: { clientId: string; clientSecret: string };
|
||||
const oAuthClientName = "test-oauth-client-admin";
|
||||
const oAuthClientName = `oauth-clients-admin-${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
membership = await membershipFixtures.addUserToOrg(user, org, "ADMIN", true);
|
||||
@@ -264,7 +265,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
});
|
||||
});
|
||||
it(`/PUT/:id`, () => {
|
||||
const clientUpdatedName = "test-oauth-client-updated";
|
||||
const clientUpdatedName = `oauth-clients-admin-updated-${randomString()}`;
|
||||
const body: UpdateOAuthClientInput = { name: clientUpdatedName };
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/oauth-clients/${client.clientId}`)
|
||||
@@ -289,7 +290,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
describe("User is part of an organization as Owner", () => {
|
||||
let membership: Membership;
|
||||
let client: { clientId: string; clientSecret: string };
|
||||
const oAuthClientName = "test-oauth-client-owner";
|
||||
const oAuthClientName = `oauth-clients-owner-${randomString()}`;
|
||||
const oAuthClientPermissions = 32;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -346,7 +347,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
});
|
||||
});
|
||||
it(`/PUT/:id`, () => {
|
||||
const clientUpdatedName = "test-oauth-client-updated";
|
||||
const clientUpdatedName = `oauth-clients-owner-updated-${randomString()}`;
|
||||
const body: UpdateOAuthClientInput = { name: clientUpdatedName };
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/oauth-clients/${client.clientId}`)
|
||||
|
||||
+5
-2
@@ -19,6 +19,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
@@ -70,7 +71,7 @@ describe("OAuthFlow Endpoints", () => {
|
||||
let refreshToken: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const userEmail = "developer@platform.com";
|
||||
const userEmail = `oauth-flow-user-${randomString()}@api.com`;
|
||||
|
||||
const moduleRef: TestingModule = await withNextAuth(
|
||||
userEmail,
|
||||
@@ -92,7 +93,9 @@ describe("OAuthFlow Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `oauth-flow-organization-${randomString()}`,
|
||||
});
|
||||
await profilesRepositoryFixture.create({
|
||||
uid: "asd-asd",
|
||||
username: userEmail,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-us
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import { User, CreationSource } from "@prisma/client";
|
||||
|
||||
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
|
||||
|
||||
@@ -46,6 +46,7 @@ export class OAuthClientUsersService {
|
||||
role: "MEMBER",
|
||||
},
|
||||
],
|
||||
creationSource: CreationSource.API_V2,
|
||||
teamId: organizationId,
|
||||
isOrg: true,
|
||||
parentId: null,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
@Injectable()
|
||||
export class IsUserOOO implements CanActivate {
|
||||
constructor(private oooRepo: UserOOORepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request>();
|
||||
const oooId: string = request.params.oooId;
|
||||
const userId: string = request.params.userId;
|
||||
|
||||
if (!userId) {
|
||||
throw new ForbiddenException("No user id found in request params.");
|
||||
}
|
||||
|
||||
if (!oooId) {
|
||||
throw new ForbiddenException("No ooo entry id found in request params.");
|
||||
}
|
||||
|
||||
const ooo = await this.oooRepo.getUserOOOByIdAndUserId(Number(oooId), Number(userId));
|
||||
|
||||
if (ooo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new ForbiddenException("This OOO entry does not belong to this user.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { ApiProperty, ApiPropertyOptional, PartialType } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsDate, IsInt, IsOptional, IsString, IsEnum, isDate } from "class-validator";
|
||||
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
|
||||
export enum OutOfOfficeReason {
|
||||
UNSPECIFIED = "unspecified",
|
||||
VACATION = "vacation",
|
||||
TRAVEL = "travel",
|
||||
SICK_LEAVE = "sick",
|
||||
PUBLIC_HOLIDAY = "public_holiday",
|
||||
}
|
||||
|
||||
export type OutOfOfficeReasonType = `${OutOfOfficeReason}`;
|
||||
|
||||
const isDateString = (dateString: string) => {
|
||||
try {
|
||||
const isoDateRegex = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{3})?Z$/;
|
||||
return isoDateRegex.test(dateString);
|
||||
} catch {
|
||||
throw new BadRequestException("Invalid Date.");
|
||||
}
|
||||
};
|
||||
|
||||
export class CreateOutOfOfficeEntryDto {
|
||||
@Transform(({ value }: { value: string }) => {
|
||||
if (isDateString(value)) {
|
||||
const date = new Date(value);
|
||||
date.setUTCHours(0, 0, 0, 0);
|
||||
return date;
|
||||
}
|
||||
throw new BadRequestException("Invalid Date.");
|
||||
})
|
||||
@IsDate()
|
||||
@ApiProperty({
|
||||
description: "The start date and time of the out of office period in ISO 8601 format in UTC timezone.",
|
||||
example: "2023-05-01T00:00:00.000Z",
|
||||
})
|
||||
start!: Date;
|
||||
|
||||
@Transform(({ value }: { value: string }) => {
|
||||
if (isDateString(value)) {
|
||||
const date = new Date(value);
|
||||
date.setUTCHours(23, 59, 59, 999);
|
||||
return date;
|
||||
}
|
||||
throw new BadRequestException("Invalid Date.");
|
||||
})
|
||||
@IsDate()
|
||||
@ApiProperty({
|
||||
description: "The end date and time of the out of office period in ISO 8601 format in UTC timezone.",
|
||||
example: "2023-05-10T23:59:59.999Z",
|
||||
})
|
||||
end!: Date;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "Optional notes for the out of office entry.",
|
||||
example: "Vacation in Hawaii",
|
||||
})
|
||||
notes?: string;
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "The ID of the user covering for the out of office period, if applicable.",
|
||||
example: 2,
|
||||
})
|
||||
toUserId?: number;
|
||||
|
||||
@IsEnum(OutOfOfficeReason)
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "the reason for the out of office entry, if applicable",
|
||||
example: "vacation",
|
||||
enum: OutOfOfficeReason,
|
||||
})
|
||||
reason?: OutOfOfficeReasonType;
|
||||
}
|
||||
|
||||
export class UpdateOutOfOfficeEntryDto extends PartialType(CreateOutOfOfficeEntryDto) {}
|
||||
|
||||
export enum SortOrder {
|
||||
asc = "asc",
|
||||
desc = "desc",
|
||||
}
|
||||
type SortOrderType = keyof typeof SortOrder;
|
||||
|
||||
export class GetOutOfOfficeEntryFiltersDTO extends SkipTakePagination {
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder, {
|
||||
message: 'SortStart must be either "asc" or "desc".',
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: "Sort results by their start time in ascending or descending order.",
|
||||
example: "?sortStart=asc OR ?sortStart=desc",
|
||||
enum: SortOrder,
|
||||
})
|
||||
sortStart?: SortOrderType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder, {
|
||||
message: 'SortEnd must be either "asc" or "desc".',
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: "Sort results by their end time in ascending or descending order.",
|
||||
example: "?sortEnd=asc OR ?sortEnd=desc",
|
||||
enum: SortOrder,
|
||||
})
|
||||
sortEnd?: SortOrderType;
|
||||
}
|
||||
|
||||
export class GetOrgUsersOutOfOfficeEntryFiltersDTO extends GetOutOfOfficeEntryFiltersDTO {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter ooo entries by the user email address. user must be within your organization.",
|
||||
example: "example@domain.com",
|
||||
})
|
||||
email?: string;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { OutOfOfficeReason } from "@/modules/ooo/inputs/ooo.input";
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsInt, IsEnum, ValidateNested, IsString, IsDateString, IsOptional } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class UserOooOutputDto {
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({
|
||||
description: "The ID of the user.",
|
||||
example: 2,
|
||||
})
|
||||
readonly userId!: number;
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "The ID of the user covering for the out of office period, if applicable.",
|
||||
example: 2,
|
||||
})
|
||||
@Expose()
|
||||
readonly toUserId?: number;
|
||||
|
||||
@IsInt()
|
||||
@Expose()
|
||||
@ApiProperty({
|
||||
description: "The ID of the ooo entry.",
|
||||
example: 2,
|
||||
})
|
||||
readonly id!: number;
|
||||
|
||||
@IsString()
|
||||
@Expose()
|
||||
@ApiProperty({
|
||||
description: "The UUID of the ooo entry.",
|
||||
example: 2,
|
||||
})
|
||||
readonly uuid!: string;
|
||||
|
||||
@IsDateString()
|
||||
@ApiProperty({
|
||||
description: "The start date and time of the out of office period in ISO 8601 format in UTC timezone.",
|
||||
example: "2023-05-01T00:00:00.000Z",
|
||||
})
|
||||
@Expose()
|
||||
start!: Date;
|
||||
|
||||
@IsDateString()
|
||||
@ApiProperty({
|
||||
description: "The end date and time of the out of office period in ISO 8601 format in UTC timezone.",
|
||||
example: "2023-05-10T23:59:59.999Z",
|
||||
})
|
||||
@Expose()
|
||||
end!: Date;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "Optional notes for the out of office entry.",
|
||||
example: "Vacation in Hawaii",
|
||||
})
|
||||
@Expose()
|
||||
notes?: string;
|
||||
|
||||
@IsEnum(OutOfOfficeReason)
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional({
|
||||
description: "the reason for the out of office entry, if applicable",
|
||||
example: "vacation",
|
||||
enum: OutOfOfficeReason,
|
||||
})
|
||||
@Expose()
|
||||
reason?: OutOfOfficeReason;
|
||||
}
|
||||
|
||||
export class UserOooOutputResponseDto {
|
||||
@Expose()
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => UserOooOutputDto)
|
||||
data!: UserOooOutputDto;
|
||||
}
|
||||
|
||||
export class UserOoosOutputResponseDto {
|
||||
@Expose()
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => UserOooOutputDto)
|
||||
data!: UserOooOutputDto[];
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
import { PrismaWriteService } from "../../prisma/prisma-write.service";
|
||||
|
||||
type OOOInputData = Omit<Prisma.OutOfOfficeEntryCreateInput, "user" | "toUser" | "reason" | "uuid"> & {
|
||||
toUserId?: number;
|
||||
userId: number;
|
||||
reasonId?: number;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UserOOORepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createUserOOO(data: OOOInputData) {
|
||||
const uuid = uuidv4();
|
||||
return this.dbWrite.prisma.outOfOfficeEntry.create({
|
||||
data: { ...data, uuid },
|
||||
include: { reason: true },
|
||||
});
|
||||
}
|
||||
|
||||
async updateUserOOO(oooId: number, data: Partial<OOOInputData>) {
|
||||
return this.dbWrite.prisma.outOfOfficeEntry.update({
|
||||
where: { id: oooId },
|
||||
data,
|
||||
include: { reason: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserOOOById(oooId: number) {
|
||||
return this.dbRead.prisma.outOfOfficeEntry.findFirst({
|
||||
where: { id: oooId },
|
||||
include: { reason: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserOOOByIdAndUserId(oooId: number, userId: number) {
|
||||
return this.dbRead.prisma.outOfOfficeEntry.findFirst({
|
||||
where: { id: oooId, userId },
|
||||
include: { reason: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserOOOPaginated(
|
||||
userId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
sort?: { sortStart?: "asc" | "desc"; sortEnd?: "asc" | "desc" }
|
||||
) {
|
||||
return this.dbRead.prisma.outOfOfficeEntry.findMany({
|
||||
where: { userId },
|
||||
skip,
|
||||
take,
|
||||
include: { reason: true },
|
||||
...(sort?.sortStart && { orderBy: { start: sort.sortStart } }),
|
||||
...(sort?.sortEnd && { orderBy: { end: sort.sortEnd } }),
|
||||
});
|
||||
}
|
||||
|
||||
async deleteUserOOO(oooId: number) {
|
||||
return this.dbWrite.prisma.outOfOfficeEntry.delete({
|
||||
where: { id: oooId },
|
||||
include: { reason: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findExistingOooRedirect(userId: number, start: Date, end: Date, toUserId?: number) {
|
||||
const existingOutOfOfficeEntry = await this.dbRead.prisma.outOfOfficeEntry.findFirst({
|
||||
select: {
|
||||
userId: true,
|
||||
toUserId: true,
|
||||
},
|
||||
where: {
|
||||
...(toUserId && { userId: toUserId }),
|
||||
toUserId: userId,
|
||||
// Check for time overlap or collision
|
||||
OR: [
|
||||
// Outside of range
|
||||
{
|
||||
AND: [{ start: { lte: end } }, { end: { gte: start } }],
|
||||
},
|
||||
// Inside of range
|
||||
{
|
||||
AND: [{ start: { gte: start } }, { end: { lte: end } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
return existingOutOfOfficeEntry;
|
||||
}
|
||||
|
||||
async getOooByUserIdAndTime(userId: number, start: Date, end: Date) {
|
||||
return await this.dbRead.prisma.outOfOfficeEntry.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
start,
|
||||
end,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
CreateOutOfOfficeEntryDto,
|
||||
UpdateOutOfOfficeEntryDto,
|
||||
OutOfOfficeReason,
|
||||
GetOutOfOfficeEntryFiltersDTO,
|
||||
SortOrder,
|
||||
} from "@/modules/ooo/inputs/ooo.input";
|
||||
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
|
||||
|
||||
import { OutOfOfficeEntry } from "@calcom/prisma/client";
|
||||
|
||||
const OOO_REASON_ID_TO_REASON = {
|
||||
1: OutOfOfficeReason["UNSPECIFIED"],
|
||||
2: OutOfOfficeReason["VACATION"],
|
||||
3: OutOfOfficeReason["TRAVEL"],
|
||||
4: OutOfOfficeReason["SICK_LEAVE"],
|
||||
5: OutOfOfficeReason["PUBLIC_HOLIDAY"],
|
||||
};
|
||||
|
||||
const OOO_REASON_TO_REASON_ID = {
|
||||
[OutOfOfficeReason["UNSPECIFIED"]]: 1,
|
||||
[OutOfOfficeReason["VACATION"]]: 2,
|
||||
[OutOfOfficeReason["TRAVEL"]]: 3,
|
||||
[OutOfOfficeReason["SICK_LEAVE"]]: 4,
|
||||
[OutOfOfficeReason["PUBLIC_HOLIDAY"]]: 5,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UserOOOService {
|
||||
constructor(
|
||||
private readonly oooRepository: UserOOORepository,
|
||||
private readonly usersRepository: UsersRepository
|
||||
) {}
|
||||
|
||||
formatOooReason(ooo: OutOfOfficeEntry) {
|
||||
return {
|
||||
...ooo,
|
||||
reason: ooo.reasonId
|
||||
? OOO_REASON_ID_TO_REASON[ooo.reasonId as keyof typeof OOO_REASON_ID_TO_REASON]
|
||||
: OOO_REASON_ID_TO_REASON[1],
|
||||
};
|
||||
}
|
||||
|
||||
isStartBeforeEnd(start?: Date, end?: Date) {
|
||||
if ((end && !start) || (start && !end)) {
|
||||
throw new BadRequestException("Please specify both ooo start and end time.");
|
||||
}
|
||||
|
||||
if (start && end) {
|
||||
if (start.getTime() > end.getTime()) {
|
||||
throw new BadRequestException("Start date must be before end date.");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async checkUserEligibleForRedirect(userId: number, toUserId?: number) {
|
||||
if (toUserId) {
|
||||
const user = await this.usersRepository.findUserOOORedirectEligible(userId, toUserId);
|
||||
if (!user) {
|
||||
throw new BadRequestException("Cannot redirect to this user.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkExistingOooRedirect(userId: number, start?: Date, end?: Date, toUserId?: number) {
|
||||
if (start && end) {
|
||||
const existingOooRedirect = await this.oooRepository.findExistingOooRedirect(
|
||||
userId,
|
||||
start,
|
||||
end,
|
||||
toUserId
|
||||
);
|
||||
|
||||
if (existingOooRedirect) {
|
||||
throw new BadRequestException("Booking redirect infinite not allowed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkDuplicateOOOEntry(userId: number, start?: Date, end?: Date) {
|
||||
if (start && end) {
|
||||
const duplicateEntry = await this.oooRepository.getOooByUserIdAndTime(userId, start, end);
|
||||
|
||||
if (duplicateEntry) {
|
||||
throw new ConflictException("Ooo entry already exists.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkRedirectToSelf(userId: number, toUserId?: number) {
|
||||
if (toUserId && toUserId === userId) {
|
||||
throw new BadRequestException("Cannot redirect to self.");
|
||||
}
|
||||
}
|
||||
|
||||
async checkIsValidOOO(userId: number, ooo: CreateOutOfOfficeEntryDto | UpdateOutOfOfficeEntryDto) {
|
||||
this.isStartBeforeEnd(ooo.start, ooo.end);
|
||||
await this.checkExistingOooRedirect(userId, ooo.start, ooo.end, ooo.toUserId);
|
||||
await this.checkDuplicateOOOEntry(userId, ooo.start, ooo.end);
|
||||
await this.checkRedirectToSelf(userId, ooo.toUserId);
|
||||
await this.checkUserEligibleForRedirect(userId, ooo.toUserId);
|
||||
}
|
||||
|
||||
async createUserOOO(userId: number, body: CreateOutOfOfficeEntryDto) {
|
||||
await this.checkIsValidOOO(userId, body);
|
||||
const { reason, ...rest } = body;
|
||||
const ooo = await this.oooRepository.createUserOOO({
|
||||
...rest,
|
||||
userId,
|
||||
reasonId: OOO_REASON_TO_REASON_ID[reason ?? OutOfOfficeReason["UNSPECIFIED"]],
|
||||
});
|
||||
return this.formatOooReason(ooo);
|
||||
}
|
||||
|
||||
async updateUserOOO(userId: number, oooId: number, body: UpdateOutOfOfficeEntryDto) {
|
||||
await this.checkIsValidOOO(userId, body);
|
||||
const { reason, ...rest } = body;
|
||||
const data = reason
|
||||
? { ...rest, reasonId: OOO_REASON_TO_REASON_ID[reason ?? OutOfOfficeReason["UNSPECIFIED"]] }
|
||||
: rest;
|
||||
const ooo = await this.oooRepository.updateUserOOO(oooId, data);
|
||||
return this.formatOooReason(ooo);
|
||||
}
|
||||
|
||||
async deleteUserOOO(oooId: number) {
|
||||
const ooo = await this.oooRepository.deleteUserOOO(oooId);
|
||||
return this.formatOooReason(ooo);
|
||||
}
|
||||
|
||||
async getUserOOOPaginated(
|
||||
userId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
sort?: { sortStart?: "asc" | "desc"; sortEnd?: "asc" | "desc" }
|
||||
) {
|
||||
const ooos = await this.oooRepository.getUserOOOPaginated(userId, skip, take, sort);
|
||||
return ooos.map((ooo) => this.formatOooReason(ooo));
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -14,6 +14,7 @@ import { AttributeRepositoryFixture } from "test/fixtures/repository/attributes.
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -26,7 +27,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "member@attributes-options-api.com";
|
||||
const userEmail = `organization-attributes-options-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -45,7 +46,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesOptionsCorp",
|
||||
name: `organization-attributes-options-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -97,7 +98,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let attributeRepositoryFixture: AttributeRepositoryFixture;
|
||||
|
||||
const userEmail = "admin@attributes-options-api.com";
|
||||
const userEmail = `organization-attributes-options-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -123,7 +124,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
attributeRepositoryFixture = new AttributeRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesOptionsCorp",
|
||||
name: `organization-attributes-options-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+5
-4
@@ -12,6 +12,7 @@ import * as request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -24,7 +25,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "member@attributes-api.com";
|
||||
const userEmail = `organization-attributes-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -42,7 +43,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesCorp",
|
||||
name: `organization-attributes-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -88,7 +89,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "admin@attributes-api.com";
|
||||
const userEmail = `organization-attributes-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -116,7 +117,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesCorp",
|
||||
name: `organization-attributes-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+6
-2
@@ -149,10 +149,14 @@ export class OrganizationsEventTypesController {
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Query() queryParams: GetTeamEventTypesQuery_2024_06_14
|
||||
): Promise<GetTeamEventTypesOutput> {
|
||||
const { eventSlug } = queryParams;
|
||||
const { eventSlug, hostsLimit } = queryParams;
|
||||
|
||||
if (eventSlug) {
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(teamId, eventSlug);
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(
|
||||
teamId,
|
||||
eventSlug,
|
||||
hostsLimit
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
|
||||
+122
-21
@@ -14,6 +14,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -48,12 +49,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
let falseTestOrg: Team;
|
||||
let falseTestTeam: Team;
|
||||
|
||||
const userEmail = "org-admin-event-types-controller-e222e@api.com";
|
||||
const userEmail = `organizations-event-types-admin-${randomString()}@api.com`;
|
||||
let userAdmin: User;
|
||||
|
||||
const teammate1Email = "teammate111@team.com";
|
||||
const teammate2Email = "teammate221@team.com";
|
||||
const falseTestUserEmail = "false-user@false-team.com";
|
||||
const teammate1Email = `organizations-event-types-teammate1-${randomString()}@api.com`;
|
||||
const teammate2Email = `organizations-event-types-teammate2-${randomString()}@api.com`;
|
||||
const falseTestUserEmail = `organizations-event-types-false-user-${randomString()}@api.com`;
|
||||
let teammate1: User;
|
||||
let teammate2: User;
|
||||
let falseTestUser: User;
|
||||
@@ -98,23 +99,23 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-event-types-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
falseTestOrg = await organizationsRepositoryFixture.create({
|
||||
name: "False test org",
|
||||
name: `organizations-event-types-false-org-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `organizations-event-types-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
falseTestTeam = await teamsRepositoryFixture.create({
|
||||
name: "Outside org team",
|
||||
name: `organizations-event-types-false-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: falseTestOrg.id } },
|
||||
});
|
||||
@@ -237,7 +238,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
successRedirectUrl: "https://masterchief.com/argentina/flan/video/1234",
|
||||
title: "Coding consultation collective",
|
||||
slug: "coding-consultation collective",
|
||||
slug: `organizations-event-types-collective-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -343,7 +344,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
it("should create a managed team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Coding consultation managed",
|
||||
slug: "coding-consultation-managed",
|
||||
slug: `organizations-event-types-managed-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -577,6 +578,100 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to configure phone-only event type", async () => {
|
||||
const body: UpdateTeamEventTypeInput_2024_06_14 = {
|
||||
bookingFields: [
|
||||
{
|
||||
type: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
label: "Phone number",
|
||||
hidden: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}/event-types/${collectiveEventType.id}`)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data;
|
||||
expect(data.bookingFields).toEqual([
|
||||
{
|
||||
isDefault: true,
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: false,
|
||||
label: "Email",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: true,
|
||||
hidden: true,
|
||||
label: "Phone number",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "text",
|
||||
slug: "title",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
slug: "guests",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "rescheduleReason",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should assign all members to managed event-type", async () => {
|
||||
const body: UpdateTeamEventTypeInput_2024_06_14 = {
|
||||
assignAllTeamMembers: true,
|
||||
@@ -657,10 +752,10 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
it("should return event type with default bookingFields if they are not defined", async () => {
|
||||
const eventTypeInput = {
|
||||
title: "unknown field event type two",
|
||||
slug: `organizations-event-types-unknown-${randomString()}`,
|
||||
description: "unknown field event type description two",
|
||||
length: 40,
|
||||
hidden: false,
|
||||
slug: "unknown-field-type-two",
|
||||
locations: [],
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
};
|
||||
@@ -679,13 +774,19 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{ isDefault: true, type: "phone", slug: "attendeePhoneNumber", required: false, hidden: true },
|
||||
{
|
||||
disableOnPrefill: false,
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
@@ -696,6 +797,14 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
@@ -712,14 +821,6 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
+7
-7
@@ -14,6 +14,7 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -33,10 +34,9 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
let membership2: Membership;
|
||||
let membershipCreatedViaApi: Membership;
|
||||
|
||||
const userEmail = "org-admin-membership-controller-e2e@api.com";
|
||||
const userEmail2 = "org-member-membership-controller-e2e@api.com";
|
||||
|
||||
const invitedUserEmail = "org-member-invited-membership-controller-e2e@api.com";
|
||||
const userEmail = `organizations-memberships-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-memberships-member-${randomString()}@api.com`;
|
||||
const invitedUserEmail = `organizations-memberships-invited-${randomString()}@api.com`;
|
||||
|
||||
let user: User;
|
||||
let user2: User;
|
||||
@@ -70,7 +70,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -217,7 +217,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
|
||||
const userEmail = "org-member-memberships-controller-e2e@api.com";
|
||||
const userEmail = `organizations-memberships-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -238,7 +238,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+11
-7
@@ -12,6 +12,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -35,7 +36,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "mr-robot@schedules-api.com";
|
||||
const userEmail = `organizations-schedules-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -53,7 +54,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Ecorp",
|
||||
name: `organizations-schedules-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -115,12 +116,15 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const userEmail = "mr-robot@schedules-api.com";
|
||||
const username = "mr-robot";
|
||||
const userEmail = `organizations-schedules-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-schedules-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let user2: User;
|
||||
let org: Team;
|
||||
let profile: Profile;
|
||||
let membership: Membership;
|
||||
let membership2: Membership;
|
||||
let profile: Profile;
|
||||
let profile2: Profile;
|
||||
|
||||
let createdSchedule: ScheduleOutput_2024_06_11;
|
||||
|
||||
@@ -152,7 +156,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Ecorp",
|
||||
name: `organizations-schedules-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -164,7 +168,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
|
||||
profile = await profileRepositoryFixture.create({
|
||||
uid: `usr-${user.id}`,
|
||||
username: username,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
|
||||
+8
-8
@@ -17,6 +17,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -44,11 +45,10 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
let membership2: Membership;
|
||||
let membershipCreatedViaApi: OrgTeamMembershipOutputDto;
|
||||
|
||||
const userEmail = "org-admin-membership-teams-controller-e2e@api.com";
|
||||
const userEmail2 = "org-member-membership-teams-controller-e2e@api.com";
|
||||
const nonOrgUserEmail = "non-org-member-membership-teams-controller-e2e@api.com";
|
||||
|
||||
const invitedUserEmail = "org-member-invited-membership-teams-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-memberships-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-teams-memberships-member-${randomString()}@api.com`;
|
||||
const nonOrgUserEmail = `organizations-teams-memberships-non-org-${randomString()}@api.com`;
|
||||
const invitedUserEmail = `organizations-teams-memberships-invited-${randomString()}@api.com`;
|
||||
|
||||
let user: User;
|
||||
let user2: User;
|
||||
@@ -92,12 +92,12 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-teams-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
orgTeam = await teamsRepositoryFixture.create({
|
||||
name: "Org Team",
|
||||
name: `organizations-teams-memberships-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
@@ -129,7 +129,7 @@ describe("Organizations Teams Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
nonOrgTeam = await teamsRepositoryFixture.create({
|
||||
name: "Non Org Team",
|
||||
name: `organizations-teams-memberships-non-org-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
});
|
||||
|
||||
|
||||
+30
-26
@@ -15,6 +15,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS, X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
@@ -36,7 +37,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
let teamCreatedViaApi: Team;
|
||||
let teamCreatedViaApi2: Team;
|
||||
|
||||
const userEmail = "org-admin-teams-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -58,7 +59,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -69,13 +70,13 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: "Test org team 2",
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
@@ -133,10 +134,11 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create the team of the org", async () => {
|
||||
const teamName = `organizations-teams-api-team1-${randomString()}`;
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: "Team created via API",
|
||||
name: teamName,
|
||||
slug: "team-created-via-api",
|
||||
bio: "This is our test team created via API",
|
||||
} satisfies CreateOrgTeamDto)
|
||||
@@ -145,7 +147,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
const responseBody: ApiSuccessResponse<Team> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
teamCreatedViaApi = responseBody.data;
|
||||
expect(teamCreatedViaApi.name).toEqual("Team created via API");
|
||||
expect(teamCreatedViaApi.name).toEqual(teamName);
|
||||
expect(teamCreatedViaApi.slug).toEqual("team-created-via-api");
|
||||
expect(teamCreatedViaApi.bio).toEqual("This is our test team created via API");
|
||||
expect(teamCreatedViaApi.parentId).toEqual(org.id);
|
||||
@@ -171,10 +173,11 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update the team of the org", async () => {
|
||||
const updatedTeamName = `organizations-teams-api-team1-${randomString()}-updated`;
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${teamCreatedViaApi.id}`)
|
||||
.send({
|
||||
name: "Team created via API Updated",
|
||||
name: updatedTeamName,
|
||||
weekStart: "Monday",
|
||||
logoUrl: "https://i.cal.com/api/avatar/b0b58752-68ad-4c0d-8024-4fa382a77752.png",
|
||||
bannerUrl: "https://i.cal.com/api/avatar/949be534-7a88-4185-967c-c020b0c0bef3.png",
|
||||
@@ -184,7 +187,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
const responseBody: ApiSuccessResponse<Team> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
teamCreatedViaApi = responseBody.data;
|
||||
expect(teamCreatedViaApi.name).toEqual("Team created via API Updated");
|
||||
expect(teamCreatedViaApi.name).toEqual(updatedTeamName);
|
||||
expect(teamCreatedViaApi.weekStart).toEqual("Monday");
|
||||
expect(teamCreatedViaApi.logoUrl).toEqual(
|
||||
"https://i.cal.com/api/avatar/b0b58752-68ad-4c0d-8024-4fa382a77752.png"
|
||||
@@ -219,10 +222,11 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create the team of the org without auto-accepting creator", async () => {
|
||||
const teamName = `organizations-teams-api-team2-${randomString()}`;
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: "Team II created via API",
|
||||
name: teamName,
|
||||
autoAcceptCreator: false,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(201)
|
||||
@@ -230,7 +234,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
const responseBody: ApiSuccessResponse<Team> = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
teamCreatedViaApi2 = responseBody.data;
|
||||
expect(teamCreatedViaApi2.name).toEqual("Team II created via API");
|
||||
expect(teamCreatedViaApi2.name).toEqual(teamName);
|
||||
expect(teamCreatedViaApi2.parentId).toEqual(org.id);
|
||||
const membership = await membershipsRepositoryFixture.getUserMembershipByTeamId(
|
||||
user.id,
|
||||
@@ -265,7 +269,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
let team: Team;
|
||||
let team2: Team;
|
||||
|
||||
const userEmail = "org-member-teams-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -287,7 +291,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -298,13 +302,13 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: "Test org team 2",
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
@@ -338,7 +342,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: "Team created via API",
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
@@ -347,7 +351,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: "Team created via API Updated",
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
@@ -379,7 +383,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
let team: Team;
|
||||
let team2: Team;
|
||||
|
||||
const userEmail = "org-member-teams-owner-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-owner-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -401,7 +405,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -412,13 +416,13 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: "Test org team 2",
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
@@ -464,7 +468,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: "Team created via API",
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
@@ -473,7 +477,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: "Team created via API Updated",
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
@@ -509,7 +513,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
let team1: Team;
|
||||
let team2: Team;
|
||||
|
||||
const userEmail = "platform-org-member-teams-owner-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-platform-owner-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -531,7 +535,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await orgRepositoryFixture.create({
|
||||
name: "Platform Test Organization",
|
||||
name: `organizations-teams-platform-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -570,7 +574,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create first oAuth client team", async () => {
|
||||
const teamName = "Platform team created via API";
|
||||
const teamName = `organizations-teams-platform-api-team1-${randomString()}`;
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.set(X_CAL_CLIENT_ID, oAuthClient1.id)
|
||||
@@ -593,7 +597,7 @@ describe("Organizations Team Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create second oAuth client team", async () => {
|
||||
const teamName = "Platform team II created via API";
|
||||
const teamName = `organizations-teams-platform-api-team2-${randomString()}`;
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.set(X_CAL_CLIENT_ID, oAuthClient2.id)
|
||||
|
||||
+9
-9
@@ -14,6 +14,7 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -37,11 +38,10 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
||||
let nonOrgTeam: Team;
|
||||
let user2Schedule: Schedule;
|
||||
|
||||
const userEmail = "org-admin-schedules-teams-controller-e2e@api.com";
|
||||
const userEmail2 = "org-member-schedules-teams-controller-e2e@api.com";
|
||||
const nonOrgUserEmail = "non-org-member-schedules-teams-controller-e2e@api.com";
|
||||
|
||||
const invitedUserEmail = "org-member-invited-schedules-teams-controller-e2e@api.com";
|
||||
const userEmail = `organizations-teams-schedules-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-teams-schedules-member-${randomString()}@api.com`;
|
||||
const nonOrgUserEmail = `organizations-teams-schedules-non-org-${randomString()}@api.com`;
|
||||
const invitedUserEmail = `organizations-teams-schedules-invited-${randomString()}@api.com`;
|
||||
|
||||
let user: User;
|
||||
let user2: User;
|
||||
@@ -79,7 +79,7 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
||||
id: user2.id,
|
||||
},
|
||||
},
|
||||
name: "User 2 schedule",
|
||||
name: `organizations-teams-schedules-user2-schedule-${randomString()}`,
|
||||
timeZone: "America/New_York",
|
||||
});
|
||||
|
||||
@@ -94,18 +94,18 @@ describe("Organizations Teams Schedules Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-teams-schedules-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
orgTeam = await teamsRepositoryFixture.create({
|
||||
name: "Org Team",
|
||||
name: `organizations-teams-schedules-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
nonOrgTeam = await teamsRepositoryFixture.create({
|
||||
name: "Non Org Team",
|
||||
name: `organizations-teams-schedules-non-org-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
});
|
||||
|
||||
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
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 { IsUserInOrg } from "@/modules/auth/guards/users/is-user-in-org.guard";
|
||||
import { IsUserOOO } from "@/modules/ooo/guards/is-user-ooo";
|
||||
import {
|
||||
CreateOutOfOfficeEntryDto,
|
||||
UpdateOutOfOfficeEntryDto,
|
||||
GetOutOfOfficeEntryFiltersDTO,
|
||||
GetOrgUsersOutOfOfficeEntryFiltersDTO,
|
||||
} from "@/modules/ooo/inputs/ooo.input";
|
||||
import {
|
||||
UserOooOutputDto,
|
||||
UserOooOutputResponseDto,
|
||||
UserOoosOutputResponseDto,
|
||||
} from "@/modules/ooo/outputs/ooo.output";
|
||||
import { UserOOOService } from "@/modules/ooo/services/ooo.service";
|
||||
import { OrgUsersOOOService } from "@/modules/organizations/controllers/users/ooo/services/organization-users-ooo.service";
|
||||
import {
|
||||
Controller,
|
||||
UseGuards,
|
||||
Get,
|
||||
Post,
|
||||
Patch,
|
||||
Delete,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Body,
|
||||
UseInterceptors,
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { ClassSerializerInterceptor } from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@UseGuards(ApiAuthGuard, IsOrgGuard, RolesGuard, PlatformPlanGuard, IsAdminAPIEnabledGuard)
|
||||
@UseGuards(IsOrgGuard)
|
||||
@DocsTags("Orgs / Users / OOO")
|
||||
export class OrganizationsUsersOOOController {
|
||||
constructor(
|
||||
private readonly userOOOService: UserOOOService,
|
||||
private readonly orgUsersOOOService: OrgUsersOOOService
|
||||
) {}
|
||||
|
||||
@Get("/users/:userId/ooo")
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@UseGuards(IsUserInOrg)
|
||||
@ApiOperation({ summary: "Get all ooo entries of a user" })
|
||||
async getOrganizationUserOOO(
|
||||
@Param("userId", ParseIntPipe) userId: number,
|
||||
@Query() query: GetOutOfOfficeEntryFiltersDTO
|
||||
): Promise<UserOoosOutputResponseDto> {
|
||||
const { skip, take, ...rest } = query ?? { skip: 0, take: 250 };
|
||||
const ooos = await this.userOOOService.getUserOOOPaginated(userId, skip ?? 0, take ?? 250, rest);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: ooos.map((ooo) => plainToInstance(UserOooOutputDto, ooo, { strategy: "excludeAll" })),
|
||||
};
|
||||
}
|
||||
|
||||
@Post("/users/:userId/ooo")
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@UseGuards(IsUserInOrg)
|
||||
@ApiOperation({ summary: "Create an ooo entry for user" })
|
||||
async createOrganizationUserOOO(
|
||||
@Param("userId", ParseIntPipe) userId: number,
|
||||
@Body() input: CreateOutOfOfficeEntryDto
|
||||
): Promise<UserOooOutputResponseDto> {
|
||||
const ooo = await this.userOOOService.createUserOOO(userId, input);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToInstance(UserOooOutputDto, ooo, { strategy: "excludeAll" }),
|
||||
};
|
||||
}
|
||||
|
||||
@Patch("/users/:userId/ooo/:oooId")
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@UseGuards(IsUserInOrg, IsUserOOO)
|
||||
@ApiOperation({ summary: "Update ooo entry of a user" })
|
||||
async updateOrganizationUserOOO(
|
||||
@Param("userId", ParseIntPipe) userId: number,
|
||||
@Param("oooId", ParseIntPipe) oooId: number,
|
||||
|
||||
@Body() input: UpdateOutOfOfficeEntryDto
|
||||
): Promise<UserOooOutputResponseDto> {
|
||||
const ooo = await this.userOOOService.updateUserOOO(userId, oooId, input);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToInstance(UserOooOutputDto, ooo, { strategy: "excludeAll" }),
|
||||
};
|
||||
}
|
||||
|
||||
@Delete("/users/:userId/ooo/:oooId")
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@UseGuards(IsUserInOrg, IsUserOOO)
|
||||
@ApiOperation({ summary: "Delete ooo entry of a user" })
|
||||
async deleteOrganizationUserOOO(
|
||||
@Param("oooId", ParseIntPipe) oooId: number
|
||||
): Promise<UserOooOutputResponseDto> {
|
||||
const ooo = await this.userOOOService.deleteUserOOO(oooId);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToInstance(UserOooOutputDto, ooo, { strategy: "excludeAll" }),
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/ooo")
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@ApiOperation({ summary: "Get all OOO entries of org users" })
|
||||
async getOrganizationUsersOOO(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Query() query: GetOrgUsersOutOfOfficeEntryFiltersDTO
|
||||
): Promise<UserOoosOutputResponseDto> {
|
||||
const { skip, take, email, ...rest } = query ?? { skip: 0, take: 250 };
|
||||
const ooos = await this.orgUsersOOOService.getOrgUsersOOOPaginated(orgId, skip ?? 0, take ?? 250, rest, {
|
||||
email,
|
||||
});
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: ooos.map((ooo) => plainToInstance(UserOooOutputDto, ooo, { strategy: "excludeAll" })),
|
||||
};
|
||||
}
|
||||
}
|
||||
+446
@@ -0,0 +1,446 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { UserOooOutputDto } from "@/modules/ooo/outputs/ooo.output";
|
||||
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 { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
describe("Organizations User OOO Endpoints", () => {
|
||||
describe("User Authentication - User is Org Admin", () => {
|
||||
let app: INestApplication;
|
||||
let oooCreatedViaApiId: number;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let teamsRepositoryFixture: TeamRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
let falseTestOrg: Team;
|
||||
let falseTestTeam: Team;
|
||||
|
||||
const userEmail = `organizations-users-ooo-admin-${randomString()}@api.com`;
|
||||
let userAdmin: User;
|
||||
|
||||
const teammate1Email = `organizations-users-ooo-member1-${randomString()}@api.com`;
|
||||
const teammate2Email = `organizations-users-ooo-member2-${randomString()}@api.com`;
|
||||
const falseTestUserEmail = `organizations-users-ooo-false-user-${randomString()}@api.com`;
|
||||
let teammate1: User;
|
||||
let teammate2: User;
|
||||
let falseTestUser: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
userAdmin = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
role: "ADMIN",
|
||||
});
|
||||
|
||||
teammate1 = await userRepositoryFixture.create({
|
||||
email: teammate1Email,
|
||||
username: teammate1Email,
|
||||
});
|
||||
|
||||
teammate2 = await userRepositoryFixture.create({
|
||||
email: teammate2Email,
|
||||
username: teammate2Email,
|
||||
});
|
||||
|
||||
falseTestUser = await userRepositoryFixture.create({
|
||||
email: falseTestUserEmail,
|
||||
username: falseTestUserEmail,
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-users-ooo-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
falseTestOrg = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-users-ooo-false-org-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-users-ooo-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
falseTestTeam = await teamsRepositoryFixture.create({
|
||||
name: `organizations-users-ooo-false-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: falseTestOrg.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${userAdmin.id}`,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: userAdmin.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${teammate1.id}`,
|
||||
username: teammate1Email,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: teammate1.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: userAdmin.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: teammate1.id } },
|
||||
team: { connect: { id: team.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: teammate2.id } },
|
||||
team: { connect: { id: team.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: falseTestUser.id } },
|
||||
team: { connect: { id: falseTestTeam.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(organizationsRepositoryFixture).toBeDefined();
|
||||
expect(userAdmin).toBeDefined();
|
||||
expect(org).toBeDefined();
|
||||
});
|
||||
|
||||
it("should create a ooo entry without redirect", async () => {
|
||||
const body = {
|
||||
start: "2025-05-01T01:00:00.000Z",
|
||||
end: "2025-05-10T13:59:59.999Z",
|
||||
notes: "ooo numero uno",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data as UserOooOutputDto;
|
||||
expect(data.reason).toEqual("unspecified");
|
||||
expect(data.userId).toEqual(teammate1.id);
|
||||
expect(data.start).toEqual("2025-05-01T00:00:00.000Z");
|
||||
expect(data.end).toEqual("2025-05-10T23:59:59.999Z");
|
||||
oooCreatedViaApiId = data.id;
|
||||
});
|
||||
});
|
||||
|
||||
it("should create a ooo entry with redirect", async () => {
|
||||
const body = {
|
||||
start: "2025-08-01T01:00:00.000Z",
|
||||
end: "2025-10-10T13:59:59.999Z",
|
||||
notes: "ooo numero dos with redirect",
|
||||
toUserId: teammate2.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data as UserOooOutputDto;
|
||||
expect(data.reason).toEqual("unspecified");
|
||||
expect(data.userId).toEqual(teammate1.id);
|
||||
expect(data.start).toEqual("2025-08-01T00:00:00.000Z");
|
||||
expect(data.end).toEqual("2025-10-10T23:59:59.999Z");
|
||||
expect(data.toUserId).toEqual(teammate2.id);
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to create a ooo entry with start after end", async () => {
|
||||
const body = {
|
||||
start: "2025-07-01T00:00:00.000Z",
|
||||
end: "2025-05-10T23:59:59.999Z",
|
||||
notes: "ooo numero uno duplicate",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should fail to create a duplicate ooo entry", async () => {
|
||||
const body = {
|
||||
start: "2025-05-01T00:00:00.000Z",
|
||||
end: "2025-05-10T23:59:59.999Z",
|
||||
notes: "ooo numero uno duplicate",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(409);
|
||||
});
|
||||
|
||||
it("should fail to create an ooo entry that redirects to self", async () => {
|
||||
const body = {
|
||||
start: "2025-05-02T00:00:00.000Z",
|
||||
end: "2025-05-03T23:59:59.999Z",
|
||||
notes: "ooo infinite redirect",
|
||||
toUserId: teammate1.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should fail to create an ooo entry that redirects to member outside of org", async () => {
|
||||
const body = {
|
||||
start: "2025-05-02T00:00:00.000Z",
|
||||
end: "2025-05-03T23:59:59.999Z",
|
||||
notes: "ooo invalid redirect",
|
||||
toUserId: falseTestUser.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should update a ooo entry without redirect", async () => {
|
||||
const body = {
|
||||
start: "2025-06-01T01:00:00.000Z",
|
||||
end: "2025-06-10T13:59:59.999Z",
|
||||
notes: "ooo numero uno",
|
||||
reason: "vacation",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data as UserOooOutputDto;
|
||||
expect(data.reason).toEqual("vacation");
|
||||
expect(data.userId).toEqual(teammate1.id);
|
||||
expect(data.start).toEqual("2025-06-01T00:00:00.000Z");
|
||||
expect(data.end).toEqual("2025-06-10T23:59:59.999Z");
|
||||
});
|
||||
});
|
||||
|
||||
it("should fail to update a ooo entry with redirect outside of org", async () => {
|
||||
const body = {
|
||||
toUserId: falseTestUser.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should fail to update a ooo entry with start after end ", async () => {
|
||||
const body = {
|
||||
start: "2025-07-01T00:00:00.000Z",
|
||||
end: "2025-05-10T23:59:59.999Z",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should fail to update a ooo entry with redirect to self ", async () => {
|
||||
const body = {
|
||||
toUserId: teammate1.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it("should fail to update a ooo entry with duplicate time", async () => {
|
||||
const body = {
|
||||
start: "2025-06-01T01:00:00.000Z",
|
||||
end: "2025-06-10T13:59:59.999Z",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(409);
|
||||
});
|
||||
|
||||
it("should update a ooo entry without redirect", async () => {
|
||||
const body = {
|
||||
toUserId: teammate2.id,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.send(body)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data as UserOooOutputDto;
|
||||
expect(data.reason).toEqual("vacation");
|
||||
expect(data.toUserId).toEqual(teammate2.id);
|
||||
expect(data.userId).toEqual(teammate1.id);
|
||||
expect(data.start).toEqual("2025-06-01T00:00:00.000Z");
|
||||
expect(data.end).toEqual("2025-06-10T23:59:59.999Z");
|
||||
});
|
||||
});
|
||||
|
||||
it("should get 2 ooo entries", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/ooo?sortEnd=desc&email=${teammate1Email}`)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data as UserOooOutputDto[];
|
||||
expect(data.length).toEqual(2);
|
||||
const oooUno = data.find((ooo) => ooo.id === oooCreatedViaApiId);
|
||||
expect(oooUno).toBeDefined();
|
||||
if (oooUno) {
|
||||
expect(oooUno.reason).toEqual("vacation");
|
||||
expect(oooUno.toUserId).toEqual(teammate2.id);
|
||||
expect(oooUno.userId).toEqual(teammate1.id);
|
||||
expect(oooUno.start).toEqual("2025-06-01T00:00:00.000Z");
|
||||
expect(oooUno.end).toEqual("2025-06-10T23:59:59.999Z");
|
||||
}
|
||||
// test sort
|
||||
expect(data[1].id).toEqual(oooCreatedViaApiId);
|
||||
});
|
||||
});
|
||||
|
||||
it("should get 2 ooo entries", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
|
||||
const data = responseBody.data as UserOooOutputDto[];
|
||||
expect(data.length).toEqual(2);
|
||||
const oooUno = data.find((ooo) => ooo.id === oooCreatedViaApiId);
|
||||
expect(oooUno).toBeDefined();
|
||||
if (oooUno) {
|
||||
expect(oooUno.reason).toEqual("vacation");
|
||||
expect(oooUno.toUserId).toEqual(teammate2.id);
|
||||
expect(oooUno.userId).toEqual(teammate1.id);
|
||||
expect(oooUno.start).toEqual("2025-06-01T00:00:00.000Z");
|
||||
expect(oooUno.end).toEqual("2025-06-10T23:59:59.999Z");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should delete ooo entry", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.delete(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo/${oooCreatedViaApiId}`)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it("user should have 1 ooo entries", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${org.id}/users/${teammate1.id}/ooo`)
|
||||
.expect(200)
|
||||
.then((response) => {
|
||||
const responseBody = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
const data = responseBody.data as UserOooOutputDto[];
|
||||
expect(data.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(userAdmin.email);
|
||||
await userRepositoryFixture.deleteByEmail(teammate1.email);
|
||||
await userRepositoryFixture.deleteByEmail(teammate2.email);
|
||||
await userRepositoryFixture.deleteByEmail(falseTestUser.email);
|
||||
await teamsRepositoryFixture.delete(team.id);
|
||||
await teamsRepositoryFixture.delete(falseTestTeam.id);
|
||||
await organizationsRepositoryFixture.delete(org.id);
|
||||
await organizationsRepositoryFixture.delete(falseTestOrg.id);
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrgUsersOOORepository {
|
||||
constructor(private readonly dbRead: PrismaReadService) {}
|
||||
async getOrgUsersOOOPaginated(
|
||||
orgId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
sort?: { sortStart?: "asc" | "desc"; sortEnd?: "asc" | "desc" },
|
||||
filters?: { email?: string }
|
||||
) {
|
||||
console.log({ sort, filters });
|
||||
return this.dbRead.prisma.outOfOfficeEntry.findMany({
|
||||
where: {
|
||||
user: {
|
||||
...(filters?.email && { email: filters.email }),
|
||||
profiles: {
|
||||
some: {
|
||||
organizationId: orgId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
include: { reason: true },
|
||||
...(sort?.sortStart && { orderBy: { start: sort.sortStart } }),
|
||||
...(sort?.sortEnd && { orderBy: { end: sort.sortEnd } }),
|
||||
});
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
|
||||
import { UserOOOService } from "@/modules/ooo/services/ooo.service";
|
||||
import { OrgUsersOOORepository } from "@/modules/organizations/controllers/users/ooo/repositories/organizations-users-ooo.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OrgUsersOOOService {
|
||||
constructor(
|
||||
private readonly oooRepository: UserOOORepository,
|
||||
private readonly oooUserService: UserOOOService,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly orgUsersOOORepository: OrgUsersOOORepository
|
||||
) {}
|
||||
|
||||
async getOrgUsersOOOPaginated(
|
||||
orgId: number,
|
||||
skip: number,
|
||||
take: number,
|
||||
sort?: { sortStart?: "asc" | "desc"; sortEnd?: "asc" | "desc" },
|
||||
filters?: { email?: string }
|
||||
) {
|
||||
const ooos = await this.orgUsersOOORepository.getOrgUsersOOOPaginated(orgId, skip, take, sort, filters);
|
||||
return ooos.map((ooo) => this.oooUserService.formatOooReason(ooo));
|
||||
}
|
||||
}
|
||||
+21
-20
@@ -15,6 +15,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -29,7 +30,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const userEmail = "member1@org.com";
|
||||
const userEmail = `organizations-users-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
|
||||
@@ -47,7 +48,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test org 3",
|
||||
name: `organizations-users-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -118,24 +119,24 @@ describe("Organizations Users Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "admin1@org.com";
|
||||
const nonMemberEmail = "non-member@test.com";
|
||||
const userEmail = `organizations-users-admin-${randomString()}@api.com`;
|
||||
const nonMemberEmail = `organizations-users-non-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let createdUser: User;
|
||||
|
||||
const orgMembersData = [
|
||||
{
|
||||
email: "member1@org.com",
|
||||
username: "member1@org.com",
|
||||
email: `organizations-users-member1-${randomString()}@api.com`,
|
||||
username: `organizations-users-member1-${randomString()}@api.com`,
|
||||
},
|
||||
{
|
||||
email: "member2@org.com",
|
||||
username: "member2@org.com",
|
||||
email: `organizations-users-member2-${randomString()}@api.com`,
|
||||
username: `organizations-users-member2-${randomString()}@api.com`,
|
||||
},
|
||||
{
|
||||
email: "member3@org.com",
|
||||
username: "member3@org.com",
|
||||
email: `organizations-users-member3-${randomString()}@api.com`,
|
||||
username: `organizations-users-member3-${randomString()}@api.com`,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -154,7 +155,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test org 2",
|
||||
name: `organizations-users-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -243,9 +244,9 @@ describe("Organizations Users Endpoints", () => {
|
||||
{ userData },
|
||||
userData.map((u) => u.profile)
|
||||
);
|
||||
expect(userData.find((u) => u.profile.username === "member1@org.com")).toBeDefined();
|
||||
expect(userData.find((u) => u.profile.username === "member2@org.com")).toBeDefined();
|
||||
expect(userData.find((u) => u.profile.username === "member3@org.com")).toBeDefined();
|
||||
expect(userData.find((u) => u.profile.username === orgMembersData[0].username)).toBeDefined();
|
||||
expect(userData.find((u) => u.profile.username === orgMembersData[1].username)).toBeDefined();
|
||||
expect(userData.find((u) => u.profile.username === orgMembersData[2].username)).toBeDefined();
|
||||
|
||||
expect(userData.filter((user: { email: string }) => user.email === nonMemberEmail).length).toBe(0);
|
||||
});
|
||||
@@ -304,7 +305,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
|
||||
it("should create a new org user", async () => {
|
||||
const newOrgUser = {
|
||||
email: "new-org-member-b@org.com",
|
||||
email: `organizations-users-new-member-${randomString()}@api.com`,
|
||||
organizationRole: "MEMBER",
|
||||
autoAccept: true,
|
||||
};
|
||||
@@ -327,7 +328,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
usernameOrEmail: newOrgUser.email,
|
||||
orgName: org.name,
|
||||
orgId: org.id,
|
||||
inviterName: "admin1@org.com",
|
||||
inviterName: userEmail,
|
||||
locale: null,
|
||||
});
|
||||
createdUser = userData;
|
||||
@@ -366,7 +367,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const authEmail = "auth@org.com";
|
||||
const authEmail = `organizations-users-auth-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let team: Team;
|
||||
@@ -390,12 +391,12 @@ describe("Organizations Users Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test org 4",
|
||||
name: `organizations-users-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org 4 team",
|
||||
name: `organizations-users-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
@@ -464,7 +465,7 @@ describe("Organizations Users Endpoints", () => {
|
||||
|
||||
it("should create a new org user with team event-types", async () => {
|
||||
const newOrgUser = {
|
||||
email: "new-org-member-d@org.com",
|
||||
email: `organizations-users-new-member-${randomString()}@api.com`,
|
||||
organizationRole: "MEMBER",
|
||||
autoAccept: true,
|
||||
};
|
||||
|
||||
+3
-2
@@ -17,13 +17,14 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { Team, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "webhook-controller-e2e@api.com";
|
||||
const userEmail = `organizations-webhooks-admin-${randomString()}@api.com`;
|
||||
let org: Team;
|
||||
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
@@ -53,7 +54,7 @@ describe("WebhooksController (e2e)", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-webhooks-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/
|
||||
import { EmailModule } from "@/modules/email/email.module";
|
||||
import { EmailService } from "@/modules/email/email.service";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { UserOOORepository } from "@/modules/ooo/repositories/ooo.repository";
|
||||
import { UserOOOService } from "@/modules/ooo/services/ooo.service";
|
||||
import { OrganizationsOptionsAttributesController } from "@/modules/organizations/controllers/attributes/organizations-attributes-options.controller";
|
||||
import { OrganizationsAttributesController } from "@/modules/organizations/controllers/attributes/organizations-attributes.controller";
|
||||
import { OrganizationsEventTypesController } from "@/modules/organizations/controllers/event-types/organizations-event-types.controller";
|
||||
@@ -12,6 +14,9 @@ import { OrganizationsSchedulesController } from "@/modules/organizations/contro
|
||||
import { OrganizationsTeamsMembershipsController } from "@/modules/organizations/controllers/teams/memberships/organizations-teams-memberships.controller";
|
||||
import { OrganizationsTeamsController } from "@/modules/organizations/controllers/teams/organizations-teams.controller";
|
||||
import { OrganizationsTeamsSchedulesController } from "@/modules/organizations/controllers/teams/schedules/organizations-teams-schedules.controller";
|
||||
import { OrganizationsUsersOOOController } from "@/modules/organizations/controllers/users/ooo/organizations-users-ooo-controller";
|
||||
import { OrgUsersOOORepository } from "@/modules/organizations/controllers/users/ooo/repositories/organizations-users-ooo.repository";
|
||||
import { OrgUsersOOOService } from "@/modules/organizations/controllers/users/ooo/services/organization-users-ooo.service";
|
||||
import { OrganizationsUsersController } from "@/modules/organizations/controllers/users/organizations-users.controller";
|
||||
import { OrganizationsWebhooksController } from "@/modules/organizations/controllers/webhooks/organizations-webhooks.controller";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
@@ -86,6 +91,10 @@ import { Module } from "@nestjs/common";
|
||||
WebhooksRepository,
|
||||
WebhooksService,
|
||||
OutputTeamEventTypesResponsePipe,
|
||||
UserOOOService,
|
||||
UserOOORepository,
|
||||
OrgUsersOOOService,
|
||||
OrgUsersOOORepository,
|
||||
],
|
||||
exports: [
|
||||
OrganizationsService,
|
||||
@@ -118,6 +127,7 @@ import { Module } from "@nestjs/common";
|
||||
OrganizationsOptionsAttributesController,
|
||||
OrganizationsWebhooksController,
|
||||
OrganizationsTeamsSchedulesController,
|
||||
OrganizationsUsersOOOController,
|
||||
],
|
||||
})
|
||||
export class OrganizationsModule {}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { UpdateOrganizationUserInput } from "@/modules/organizations/inputs/upda
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { CreationSource } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsUsersRepository {
|
||||
@@ -70,7 +71,7 @@ export class OrganizationsUsersRepository {
|
||||
|
||||
async createOrganizationUser(orgId: number, createUserBody: CreateOrganizationUserInput) {
|
||||
const createdUser = await this.dbWrite.prisma.user.create({
|
||||
data: createUserBody,
|
||||
data: { ...createUserBody, creationSource: CreationSource.API_V2 },
|
||||
});
|
||||
|
||||
return createdUser;
|
||||
|
||||
+6
-2
@@ -64,8 +64,12 @@ export class OrganizationsEventTypesService {
|
||||
return this.teamsEventTypesService.getTeamEventType(teamId, eventTypeId);
|
||||
}
|
||||
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string): Promise<DatabaseTeamEventType | null> {
|
||||
return this.teamsEventTypesService.getTeamEventTypeBySlug(teamId, eventTypeSlug);
|
||||
async getTeamEventTypeBySlug(
|
||||
teamId: number,
|
||||
eventTypeSlug: string,
|
||||
hostsLimit?: number
|
||||
): Promise<DatabaseTeamEventType | null> {
|
||||
return this.teamsEventTypesService.getTeamEventTypeBySlug(teamId, eventTypeSlug, hostsLimit);
|
||||
}
|
||||
|
||||
async getTeamEventTypes(teamId: number): Promise<DatabaseTeamEventType[]> {
|
||||
|
||||
@@ -5,10 +5,10 @@ import { OrganizationsUsersRepository } from "@/modules/organizations/repositori
|
||||
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
|
||||
import { CreateUserInput } from "@/modules/users/inputs/create-user.input";
|
||||
import { Injectable, ConflictException } from "@nestjs/common";
|
||||
import { Team, CreationSource } from "@prisma/client";
|
||||
import { plainToInstance } from "class-transformer";
|
||||
|
||||
import { createNewUsersConnectToOrgIfExists } from "@calcom/platform-libraries";
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class OrganizationsUsersService {
|
||||
@@ -56,6 +56,7 @@ export class OrganizationsUsersService {
|
||||
},
|
||||
],
|
||||
teamId: org.id,
|
||||
creationSource: CreationSource.API_V2,
|
||||
isOrg: true,
|
||||
parentId: null,
|
||||
autoAcceptEmailDomain: "not-required-for-this-endpoint",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { Controller, Req, NotFoundException, Param, Post, Body } from "@nestjs/common";
|
||||
import { ApiTags as DocsTags, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
|
||||
import { getRoutedUrl } from "@calcom/platform-libraries";
|
||||
import { ApiResponse } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/router",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@DocsTags("Router controller")
|
||||
@DocsExcludeController(true)
|
||||
export class RouterController {
|
||||
@Post("/forms/:formId/submit")
|
||||
async getRoutingFormResponse(
|
||||
@Req() request: Request,
|
||||
@Param("formId") formId: string,
|
||||
@Body() body?: Record<string, string>
|
||||
): Promise<void | (ApiResponse<unknown> & { redirect: boolean })> {
|
||||
const params = Object.fromEntries(new URLSearchParams(body ?? {}));
|
||||
const routedUrlData = await getRoutedUrl({ req: request, query: { ...params, form: formId } });
|
||||
|
||||
if (routedUrlData?.notFound) {
|
||||
throw new NotFoundException("Route not found. Please check the provided form parameter.");
|
||||
}
|
||||
|
||||
if (routedUrlData?.redirect?.destination) {
|
||||
return { status: "success", data: routedUrlData?.redirect?.destination, redirect: true };
|
||||
}
|
||||
|
||||
if (routedUrlData?.props) {
|
||||
return { status: "success", data: { message: routedUrlData?.props?.message ?? "" }, redirect: false };
|
||||
}
|
||||
|
||||
return { status: "success", data: { message: "No Route nor custom message found." }, redirect: false };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { RouterController } from "@/modules/router/controllers/router.controller";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [],
|
||||
exports: [],
|
||||
controllers: [RouterController],
|
||||
})
|
||||
export class RouterModule {}
|
||||
@@ -16,7 +16,7 @@ import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.rep
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { SelectedSlotsRepositoryFixture } from "test/fixtures/repository/selected-slots.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomNumber } from "test/utils/randomNumber";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -251,7 +251,7 @@ describe("Slots Endpoints", () => {
|
||||
let selectedSlotsRepositoryFixture: SelectedSlotsRepositoryFixture;
|
||||
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||
|
||||
const userEmail = `slots-${randomNumber()}-controller-e2e@api.com`;
|
||||
const userEmail = `slots-${randomString()}-controller-e2e@api.com`;
|
||||
const userName = "bob";
|
||||
let user: User;
|
||||
let eventTypeId: number;
|
||||
@@ -291,14 +291,23 @@ describe("Slots Endpoints", () => {
|
||||
});
|
||||
|
||||
// nxte(Lauris): this creates default schedule monday to friday from 9AM to 5PM in Europe/Rome timezone
|
||||
await schedulesService.createUserDefaultSchedule(user.id, "Europe/Rome");
|
||||
const userSchedule = await schedulesService.createUserSchedule(user.id, {
|
||||
name: `slots-schedule-${randomString()}-slots.controller.e2e-spec`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
});
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "frisbee match", slug: "frisbee-match", length: 60 },
|
||||
const eventType = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: `slots-event-type-${randomString()}-slots.controller.e2e-spec`,
|
||||
slug: `slots-event-type-${randomString()}-slots.controller.e2e-spec`,
|
||||
length: 60,
|
||||
locations: [],
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
eventTypeSlug = event.slug;
|
||||
eventTypeId = eventType.id;
|
||||
eventTypeSlug = eventType.slug;
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
+13
-12
@@ -14,7 +14,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { randomNumber } from "test/utils/randomNumber";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -47,12 +47,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
let team: Team;
|
||||
let falseTestTeam: Team;
|
||||
|
||||
const userEmail = `auth-${randomNumber()}@api.com`;
|
||||
const userEmail = `teams-event-types-user-${randomString()}@api.com`;
|
||||
let userAdmin: User;
|
||||
|
||||
const teammate1Email = "teammate111@team.com";
|
||||
const teammate2Email = "teammate221@team.com";
|
||||
const falseTestUserEmail = "false-user@false-team.com";
|
||||
const teammate1Email = `teams-event-types-teammate1-${randomString()}@api.com`;
|
||||
const teammate2Email = `teams-event-types-teammate2-${randomString()}@api.com`;
|
||||
const falseTestUserEmail = `teams-event-types-false-user-${randomString()}@api.com`;
|
||||
let teamMember1: User;
|
||||
let teamMember2: User;
|
||||
let falseTestUser: User;
|
||||
@@ -97,12 +97,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `teams-event-types-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
});
|
||||
|
||||
falseTestTeam = await teamsRepositoryFixture.create({
|
||||
name: "Outside org team",
|
||||
name: `teams-event-types-false-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
});
|
||||
|
||||
@@ -169,8 +169,8 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
|
||||
it("should create a collective team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Coding consultation collective",
|
||||
slug: "coding-consultation collective",
|
||||
title: `teams-event-types-collective-${randomString()}`,
|
||||
slug: `teams-event-types-collective-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -275,8 +275,8 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
|
||||
it("should create a managed team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Coding consultation managed",
|
||||
slug: "coding-consultation-managed",
|
||||
title: `teams-event-types-managed-${randomString()}`,
|
||||
slug: `teams-event-types-managed-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -316,6 +316,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const teamEventTypes = await eventTypesRepositoryFixture.getAllTeamEventTypes(team.id);
|
||||
|
||||
expect(teammate1EventTypes.length).toEqual(1);
|
||||
expect(teammate1EventTypes[0].title).toEqual(body.title);
|
||||
expect(teammate2EventTypes.length).toEqual(1);
|
||||
expect(teamEventTypes.filter((eventType) => eventType.schedulingType === "MANAGED").length).toEqual(
|
||||
1
|
||||
@@ -423,7 +424,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update managed event-type", async () => {
|
||||
const newTitle = "Coding consultation managed updated";
|
||||
const newTitle = `teams-event-types-managed-updated-${randomString()}`;
|
||||
const newHosts: UpdateTeamEventTypeInput_2024_06_14["hosts"] = [
|
||||
{
|
||||
userId: teamMember1.id,
|
||||
|
||||
@@ -75,8 +75,16 @@ export class TeamsEventTypesService {
|
||||
return eventType;
|
||||
}
|
||||
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string): Promise<DatabaseTeamEventType | null> {
|
||||
const eventType = await this.teamsEventTypesRepository.getTeamEventTypeBySlug(teamId, eventTypeSlug);
|
||||
async getTeamEventTypeBySlug(
|
||||
teamId: number,
|
||||
eventTypeSlug: string,
|
||||
hostsLimit?: number
|
||||
): Promise<DatabaseTeamEventType | null> {
|
||||
const eventType = await this.teamsEventTypesRepository.getTeamEventTypeBySlug(
|
||||
teamId,
|
||||
eventTypeSlug,
|
||||
hostsLimit
|
||||
);
|
||||
|
||||
if (!eventType) {
|
||||
return null;
|
||||
|
||||
@@ -16,7 +16,7 @@ export class TeamsEventTypesRepository {
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string) {
|
||||
async getTeamEventTypeBySlug(teamId: number, eventTypeSlug: string, hostsLimit?: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: {
|
||||
teamId_slug: {
|
||||
@@ -27,7 +27,11 @@ export class TeamsEventTypesRepository {
|
||||
include: {
|
||||
users: true,
|
||||
schedule: true,
|
||||
hosts: true,
|
||||
hosts: hostsLimit
|
||||
? {
|
||||
take: hostsLimit,
|
||||
}
|
||||
: true,
|
||||
destinationCalendar: true,
|
||||
team: {
|
||||
select: {
|
||||
|
||||
+6
-6
@@ -24,7 +24,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
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 { randomNumber } from "test/utils/randomNumber";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { api } from "@calcom/app-store/alby";
|
||||
@@ -51,11 +51,11 @@ describe("Teams Memberships Endpoints", () => {
|
||||
let teamMemberMembership: Membership;
|
||||
let membershipCreatedViaApi: TeamMembershipOutput;
|
||||
|
||||
const teamAdminEmail = `alice-admin-${randomNumber()}@api.com`;
|
||||
const teamMemberEmail = `bob-member-${randomNumber()}@api.com`;
|
||||
const nonTeamUserEmail = `charlie-outsider-${randomNumber()}@api`;
|
||||
const teamAdminEmail = `alice-admin-${randomString()}@api.com`;
|
||||
const teamMemberEmail = `bob-member-${randomString()}@api.com`;
|
||||
const nonTeamUserEmail = `charlie-outsider-${randomString()}@api`;
|
||||
|
||||
const invitedUserEmail = `david-invited-${randomNumber()}@api.com`;
|
||||
const invitedUserEmail = `david-invited-${randomString()}@api.com`;
|
||||
|
||||
let teamAdmin: User;
|
||||
let teamMember: User;
|
||||
@@ -97,7 +97,7 @@ describe("Teams Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `Team-${randomNumber()}`,
|
||||
name: `Team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repo
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomNumber } from "test/utils/randomNumber";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { TeamOutputDto } from "@calcom/platform-types";
|
||||
@@ -29,11 +29,11 @@ describe("Teams endpoint", () => {
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let membershipRepositoryFixture: MembershipRepositoryFixture;
|
||||
|
||||
const aliceEmail = `alice-${randomNumber()}@api.com`;
|
||||
const aliceEmail = `alice-${randomString()}@api.com`;
|
||||
let alice: User;
|
||||
let aliceApiKey: string;
|
||||
|
||||
const bobEmail = `bob-${randomNumber()}@api.com`;
|
||||
const bobEmail = `bob-${randomString()}@api.com`;
|
||||
let bob: User;
|
||||
let bobApiKey: string;
|
||||
|
||||
@@ -75,7 +75,7 @@ describe("Teams endpoint", () => {
|
||||
describe("User has membership in created team", () => {
|
||||
it("should create a team", async () => {
|
||||
const body: CreateTeamInput = {
|
||||
name: "Team dog",
|
||||
name: `teams-dog-${randomString()}`,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -96,7 +96,7 @@ describe("Teams endpoint", () => {
|
||||
|
||||
it("should create a team", async () => {
|
||||
const body: CreateTeamInput = {
|
||||
name: "Team cats",
|
||||
name: `teams-cats-${randomString()}`,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -148,7 +148,7 @@ describe("Teams endpoint", () => {
|
||||
|
||||
it("should update a team", async () => {
|
||||
const body: UpdateTeamDto = {
|
||||
name: "Team dogs shepherds",
|
||||
name: `teams-dogs-shepherds-${randomString()}`,
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -223,6 +223,7 @@ describe("Teams endpoint", () => {
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(aliceEmail);
|
||||
await userRepositoryFixture.deleteByEmail(bobEmail);
|
||||
await teamRepositoryFixture.delete(team1.id);
|
||||
await teamRepositoryFixture.delete(team2.id);
|
||||
await app.close();
|
||||
|
||||
@@ -1,12 +1,25 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsOptional, Validate } from "class-validator";
|
||||
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
import { IsNumber, IsOptional, Max, Min, Validate } from "class-validator";
|
||||
|
||||
import { IsEmailStringOrArray } from "../validators/isEmailStringOrArray";
|
||||
|
||||
export class GetUsersInput extends SkipTakePagination {
|
||||
export class GetUsersInput {
|
||||
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(1000)
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@IsOptional()
|
||||
skip?: number;
|
||||
|
||||
@IsOptional()
|
||||
@Validate(IsEmailStringOrArray)
|
||||
@Transform(({ value }: { value: string | string[] }) => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user