Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38a80c90bb | ||
|
|
5f897837f2 | ||
|
|
510e73b579 | ||
|
|
1787f6d25d | ||
|
|
afa83ddb7f | ||
|
|
744359dcdf | ||
|
|
b7bf58d560 | ||
|
|
da02cd06d6 | ||
|
|
14ec3813ee | ||
|
|
288248d94f | ||
|
|
1232eefece | ||
|
|
24abcc3fb2 | ||
|
|
935e4c52a4 | ||
|
|
9a8ec4d281 | ||
|
|
259624d44d | ||
|
|
7e0845ccf4 | ||
|
|
0522af0c88 | ||
|
|
f080a59c89 | ||
|
|
76d5cefe78 | ||
|
|
252ce5dec8 | ||
|
|
98d9cd105b | ||
|
|
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 | ||
|
|
1619f2daf2 | ||
|
|
57bd0b3df3 | ||
|
|
e359d13d9a | ||
|
|
3b7cb542ed | ||
|
|
fd7f6c32ae | ||
|
|
06d8b98b15 | ||
|
|
0c69c5dfea | ||
|
|
00f7d8be0d | ||
|
|
2e9beb8513 | ||
|
|
b6c6fa7365 | ||
|
|
c623785ec2 | ||
|
|
6a381cdc79 | ||
|
|
9d4496133e | ||
|
|
1452672495 | ||
|
|
9159c591ff | ||
|
|
408aee5851 | ||
|
|
29bbada77a | ||
|
|
6178092878 | ||
|
|
6f531d0be5 | ||
|
|
d7b723c914 | ||
|
|
aad3988d60 | ||
|
|
c8172ab790 | ||
|
|
657f9dfd41 | ||
|
|
c48b209c04 | ||
|
|
b9a460d8b4 | ||
|
|
a7727b5ca6 | ||
|
|
9b661b75f1 | ||
|
|
487cc0751d | ||
|
|
f7f36978e1 | ||
|
|
c1dbb7cffd | ||
|
|
e722e75a6f | ||
|
|
455572b5f4 | ||
|
|
2fe4462f62 | ||
|
|
f9dc788007 | ||
|
|
6e293d2985 | ||
|
|
00334c5610 | ||
|
|
2f9c8cb441 | ||
|
|
39173900b1 | ||
|
|
bd61e01a5d | ||
|
|
aa0b3a2778 | ||
|
|
170feeae5e | ||
|
|
a67b57b216 | ||
|
|
821a16648f | ||
|
|
727f7df2c2 | ||
|
|
be5f7f83b5 | ||
|
|
02a49e2b4b | ||
|
|
22f92f1ee3 | ||
|
|
c7a797c0e0 | ||
|
|
447436dfff | ||
|
|
c415b2d7f5 | ||
|
|
c10c87c98e | ||
|
|
85f96f492d | ||
|
|
b1350d0764 | ||
|
|
bac2d1f6f0 | ||
|
|
fec504496a | ||
|
|
890fc92148 | ||
|
|
557bd8bf25 | ||
|
|
d8027fe248 | ||
|
|
116c765c36 | ||
|
|
c6c3e228a3 | ||
|
|
7584645b8c | ||
|
|
32ff7ffed1 | ||
|
|
55113f20fe | ||
|
|
0a293308e7 | ||
|
|
e558daaa0b | ||
|
|
3f0a6718bc | ||
|
|
8ffebdba44 | ||
|
|
cc1101ac43 | ||
|
|
7a80a5f929 | ||
|
|
a27e1498b8 | ||
|
|
a697857fa0 | ||
|
|
e2f45bdf5a | ||
|
|
d2930bc243 | ||
|
|
b0b93f612e | ||
|
|
558c8be740 | ||
|
|
1aee1742d5 | ||
|
|
ab942ed4cd | ||
|
|
b5bec79354 | ||
|
|
b48d0e1f48 | ||
|
|
4a38b4dc89 | ||
|
|
ff12e387b3 | ||
|
|
6b5e144042 | ||
|
|
cd7b93a51e | ||
|
|
a76366cbcb | ||
|
|
860c04bbda | ||
|
+6 |
225313e391 | ||
|
|
3994929a51 | ||
|
|
ae38027d76 | ||
|
|
9036080fcf | ||
|
|
e6ef4e1988 | ||
|
|
3c2619a7a3 | ||
|
|
cfb09a1b21 | ||
|
|
5fe4e09975 | ||
|
|
542799e2cf | ||
|
|
3eda7858c0 | ||
|
|
b59bf7d1ee | ||
|
|
0dadeac352 | ||
|
|
5eb7a4d56e | ||
|
|
43fa944e2f | ||
|
|
1866b2845c | ||
|
|
2cfc63e6e3 | ||
|
|
4f66f3d2c6 | ||
|
|
ca3e7bd97d | ||
|
|
cafa3f9cf3 | ||
|
|
e04faffcbb | ||
|
|
6f1556a2fd | ||
|
|
d72a954896 | ||
|
|
abeb20373c | ||
|
|
422ef2f5b8 | ||
|
|
ec920eb2a8 | ||
|
|
795eecf300 | ||
|
|
8ae3d6d55b | ||
|
|
85070574da | ||
|
|
d1c2872f6e | ||
|
|
42d78f8099 | ||
|
|
f0d7a757d4 | ||
|
|
0b2f09ab7d | ||
|
|
355aaa7010 | ||
|
|
c27b73aa9a | ||
|
|
a622c7b624 | ||
|
|
19bd3e5849 | ||
|
|
7827028e0a | ||
|
|
ebc3ced687 | ||
|
|
b56824e626 | ||
|
|
460e511459 | ||
|
|
b4fd6f3442 | ||
|
|
0a7a0e3687 | ||
|
|
595fffbea0 |
+9
-19
@@ -112,6 +112,9 @@ NEXT_PUBLIC_POSTHOG_HOST=
|
||||
PLAIN_API_KEY=
|
||||
PLAIN_API_URL=https://api.plain.com/v1
|
||||
PLAIN_HMAC_SECRET_KEY=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_ID=
|
||||
PLAIN_CHAT_HMAC_SECRET_KEY=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS=
|
||||
|
||||
# Zendesk Config
|
||||
NEXT_PUBLIC_ZENDESK_KEY=
|
||||
@@ -239,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
|
||||
@@ -352,28 +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_BOOKING_ENABLED=0
|
||||
APP_ROUTER_BOOKINGS_STATUS_ENABLED=0
|
||||
APP_ROUTER_APPS_ENABLED=0
|
||||
APP_ROUTER_TEAM_ENABLED=0
|
||||
APP_ROUTER_TEAMS_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
|
||||
|
||||
# disable setry server source maps
|
||||
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
||||
@@ -416,3 +401,8 @@ REPLEXICA_API_KEY=
|
||||
|
||||
# Comma-separated list of DSyncData.directoryId to log SCIM API requests for. It can be enabled temporarily for debugging the requests being sent to SCIM server.
|
||||
DIRECTORY_IDS_TO_LOG=
|
||||
|
||||
|
||||
# Set this when Cal.com is used to serve only one organization's booking pages
|
||||
# Read more about it in the README.md
|
||||
NEXT_PUBLIC_SINGLE_ORG_SLUG=
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/apps/api/**/* @calcom/Platform
|
||||
/deploy/**/* @calcom/Foundation
|
||||
/infra/**/* @calcom/Foundation
|
||||
/packages/app-store/applecalendar/**/* @calcom/Foundation
|
||||
/packages/app-store/caldavcalendar/**/* @calcom/Foundation
|
||||
/packages/app-store/exchange2013calendar/**/* @calcom/Foundation
|
||||
@@ -9,3 +12,9 @@
|
||||
/packages/app-store/larkcalendar/**/* @calcom/Foundation
|
||||
/packages/app-store/office365calendar/**/* @calcom/Foundation
|
||||
/packages/app-store/zohocalendar/**/* @calcom/Foundation
|
||||
/packages/core/getUserAvailability.ts @calcom/Foundation
|
||||
/packages/features/bookings/lib/handleNewBooking/**/* @calcom/Foundation
|
||||
/packages/lib/slots.ts @calcom/Foundation
|
||||
/packages/platform/**/* @calcom/Platform
|
||||
/packages/prisma/**/* @calcom/Foundation
|
||||
/packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation
|
||||
|
||||
@@ -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"
|
||||
@@ -4,8 +4,8 @@ on:
|
||||
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||
schedule:
|
||||
# Runs “every 5 minutes” (see https://crontab.guru)
|
||||
- cron: "*/5 * * * *"
|
||||
# Runs “every minute” (see https://crontab.guru)
|
||||
- cron: "* * * * *"
|
||||
jobs:
|
||||
cron-webhookTriggers:
|
||||
env:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
with:
|
||||
filters: |
|
||||
has-files-requiring-all-checks:
|
||||
- "!(**.md|.github/CODEOWNERS|docs/**|apps/web/public/static/locales/**/common.json)"
|
||||
- "!(**.md|.github/CODEOWNERS|docs/**|help/**|apps/web/public/static/locales/**/common.json)"
|
||||
- name: Get Latest Commit SHA
|
||||
id: get_sha
|
||||
run: |
|
||||
|
||||
+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
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
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
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
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@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
+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
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@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
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@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
+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
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@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
@@ -485,6 +485,15 @@ Don't code but still want to contribute? Join our [Discussions](https://github.c
|
||||
|
||||
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
|
||||
|
||||
## Single Org Mode
|
||||
If you want to have booker.yourcompany.com to be the domain used for both dashboard(e.g. https://booker.yourcompany.com/event-types) and booking pages(e.g. https://booker.yourcompany.com/john.joe/15min).
|
||||
- Set the `NEXT_PUBLIC_SINGLE_ORG_SLUG` environment variable to the slug of the organization you want to use. `NEXT_PUBLIC_SINGLE_ORG_SLUG=booker`
|
||||
- Set the `NEXT_PUBLIC_WEBAPP_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBAPP_URL=https://booker.yourcompany.com`.
|
||||
- Set the `NEXT_PUBLIC_WEBSITE_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBSITE_URL=https://booker.yourcompany.com`.
|
||||
- Set the `NEXTAUTH_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXTAUTH_URL=https://booker.yourcompany.com`.
|
||||
|
||||
Note: It causes root to serve the dashboard and not the organization profile page which shows all bookable users in the organization.
|
||||
|
||||
## Integrations
|
||||
|
||||
### Obtaining the Google API Credentials
|
||||
|
||||
@@ -11,15 +11,15 @@ test.describe("Org", () => {
|
||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||
});
|
||||
|
||||
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
||||
const response = await page.goto("https://i.cal.com/peer/embed");
|
||||
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=Peer Richelsen")).toBeVisible();
|
||||
await expect(page.locator("text=Used by Checkly")).toBeVisible();
|
||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||
});
|
||||
|
||||
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
||||
const response = await page.goto("https://i.cal.com/peer/meet/embed");
|
||||
test("Org User Event(/team-rick/test-event) Page should be embeddable", async ({ page }) => {
|
||||
const response = await page.goto("https://i.cal.com/team-rick/test-event/embed");
|
||||
expect(response?.status()).toBe(200);
|
||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||
@@ -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,17 +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,
|
||||
};
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(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);
|
||||
@@ -77,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"));
|
||||
@@ -87,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(),
|
||||
|
||||
@@ -9,11 +9,25 @@ export const schemaSelectedCalendarBaseBodyParams = SelectedCalendar;
|
||||
|
||||
export const schemaSelectedCalendarPublic = SelectedCalendar.omit({});
|
||||
|
||||
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams.partial({
|
||||
userId: true,
|
||||
});
|
||||
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||
.partial({
|
||||
userId: true,
|
||||
})
|
||||
.omit({
|
||||
// id will be set by the database
|
||||
id: true,
|
||||
// No eventTypeId support in API v1
|
||||
eventTypeId: true,
|
||||
});
|
||||
|
||||
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams.partial();
|
||||
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||
.omit({
|
||||
// id is decided by DB
|
||||
id: true,
|
||||
// No eventTypeId support in API v1
|
||||
eventTypeId: true,
|
||||
})
|
||||
.partial();
|
||||
|
||||
export const selectedCalendarIdSchema = schemaQueryIdAsString.transform((v, ctx) => {
|
||||
/** We can assume the first part is the userId since it's an integer */
|
||||
|
||||
@@ -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])
|
||||
);
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||
"start": "PORT=3003 next start",
|
||||
"docker-start-api": "PORT=80 next start",
|
||||
"type-check": "tsc --pretty --noEmit"
|
||||
"type-check": "tsc --pretty --noEmit",
|
||||
"type-check:ci": "tsc-absolute --pretty --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/tsconfig": "*",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import type { UserWithCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
|
||||
import { getConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
|
||||
import { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/lib/getConnectedDestinationCalendars";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { extractUserIdsFromQuery } from "~/lib/utils/extractUserIdsFromQuery";
|
||||
@@ -104,12 +105,8 @@ async function getHandler(req: NextApiRequest) {
|
||||
|
||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||
|
||||
const usersWithCalendars = await prisma.user.findMany({
|
||||
where: { id: { in: userIds } },
|
||||
include: {
|
||||
selectedCalendars: true,
|
||||
destinationCalendar: true,
|
||||
},
|
||||
const usersWithCalendars = await UserRepository.findManyByIdsIncludeDestinationAndSelectedCalendars({
|
||||
ids: userIds,
|
||||
});
|
||||
|
||||
return await getConnectedCalendars(usersWithCalendars);
|
||||
@@ -117,11 +114,12 @@ async function getHandler(req: NextApiRequest) {
|
||||
|
||||
async function getConnectedCalendars(users: UserWithCalendars[]) {
|
||||
const connectedDestinationCalendarsPromises = users.map((user) =>
|
||||
getConnectedDestinationCalendars(user, false, prisma).then((connectedCalendarsResult) =>
|
||||
connectedCalendarsResult.connectedCalendars.map((calendar) => ({
|
||||
userId: user.id,
|
||||
...calendar,
|
||||
}))
|
||||
getConnectedDestinationCalendarsAndEnsureDefaultsInDb({ user, onboarding: false, prisma }).then(
|
||||
(connectedCalendarsResult) =>
|
||||
connectedCalendarsResult.connectedCalendars.map((calendar) => ({
|
||||
userId: user.id,
|
||||
...calendar,
|
||||
}))
|
||||
)
|
||||
);
|
||||
const connectedDestinationCalendars = await Promise.all(connectedDestinationCalendarsPromises);
|
||||
|
||||
@@ -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.` };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
|
||||
import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
||||
|
||||
@@ -49,7 +49,9 @@ import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
||||
export async function deleteHandler(req: NextApiRequest) {
|
||||
const { query } = req;
|
||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||
await prisma.selectedCalendar.delete({ where: { userId_integration_externalId } });
|
||||
await SelectedCalendarRepository.deleteUserLevel({
|
||||
where: userId_integration_externalId,
|
||||
});
|
||||
return { message: `Selected Calendar with id: ${query.id} deleted successfully` };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
|
||||
import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
||||
|
||||
@@ -49,8 +49,8 @@ import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/va
|
||||
export async function getHandler(req: NextApiRequest) {
|
||||
const { query } = req;
|
||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||
const data = await prisma.selectedCalendar.findUniqueOrThrow({
|
||||
where: { userId_integration_externalId },
|
||||
const data = await SelectedCalendarRepository.findUserLevelUniqueOrThrow({
|
||||
where: userId_integration_externalId,
|
||||
});
|
||||
return { selected_calendar: schemaSelectedCalendarPublic.parse(data) };
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import type { UpdateArguments } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import {
|
||||
@@ -56,7 +58,7 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
const { query, isSystemWideAdmin } = req;
|
||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||
const { userId: bodyUserId, ...data } = schemaSelectedCalendarUpdateBodyParams.parse(req.body);
|
||||
const args: Prisma.SelectedCalendarUpdateArgs = { where: { userId_integration_externalId }, data };
|
||||
const args: UpdateArguments = { where: { ...userId_integration_externalId }, data };
|
||||
|
||||
if (!isSystemWideAdmin && bodyUserId)
|
||||
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
||||
@@ -67,7 +69,7 @@ export async function patchHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
const result = await prisma.selectedCalendar.update(args);
|
||||
const result = await SelectedCalendarRepository.updateUserLevel(args);
|
||||
return { selected_calendar: schemaSelectedCalendarPublic.parse(result) };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { FindManyArgs } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
|
||||
import { schemaSelectedCalendarPublic } from "~/lib/validations/selected-calendar";
|
||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||
@@ -34,7 +34,7 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
||||
async function getHandler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin } = req;
|
||||
/* Admin gets all selected calendar by default, otherwise only the user's ones */
|
||||
const args: Prisma.SelectedCalendarFindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
|
||||
const args: FindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
|
||||
|
||||
/** Only admins can query other users */
|
||||
if (!isSystemWideAdmin && req.query.userId)
|
||||
@@ -46,7 +46,7 @@ async function getHandler(req: NextApiRequest) {
|
||||
if (Array.isArray(query.userId)) args.orderBy = { userId: "asc" };
|
||||
}
|
||||
|
||||
const data = await prisma.selectedCalendar.findMany(args);
|
||||
const data = await SelectedCalendarRepository.findManyUserLevel(args);
|
||||
return { selected_calendars: data.map((v) => schemaSelectedCalendarPublic.parse(v)) };
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import {
|
||||
@@ -52,7 +53,11 @@ import {
|
||||
async function postHandler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin } = req;
|
||||
const { userId: bodyUserId, ...body } = schemaSelectedCalendarBodyParams.parse(req.body);
|
||||
const args: Prisma.SelectedCalendarCreateArgs = { data: { ...body, userId } };
|
||||
const args: {
|
||||
data: Prisma.SelectedCalendarUncheckedCreateInput;
|
||||
} = {
|
||||
data: { ...body, userId },
|
||||
};
|
||||
|
||||
if (!isSystemWideAdmin && bodyUserId)
|
||||
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
||||
@@ -63,7 +68,7 @@ async function postHandler(req: NextApiRequest) {
|
||||
args.data.userId = bodyUserId;
|
||||
}
|
||||
|
||||
const data = await prisma.selectedCalendar.create(args);
|
||||
const data = await SelectedCalendarRepository.create(args.data);
|
||||
|
||||
return {
|
||||
selected_calendar: schemaSelectedCalendarPublic.parse(data),
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ describe("POST /api/attendees", () => {
|
||||
bookingId: 1,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
||||
req.isSystemWideAdmin = true;
|
||||
req.userId = 123;
|
||||
@@ -84,7 +86,8 @@ describe("POST /api/attendees", () => {
|
||||
timeZone: "UTC",
|
||||
bookingId: 1,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
||||
|
||||
req.userId = 123;
|
||||
|
||||
@@ -11,7 +11,8 @@ import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
||||
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";
|
||||
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", () => {
|
||||
|
||||
@@ -21,6 +21,8 @@ describe("DELETE /api/event-types/[id]", () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
// Mocking membership.findFirst
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
prismaMock.membership.findFirst.mockImplementation(({ where }) => {
|
||||
const { userId, teamId, accepted, role } = where;
|
||||
const mockData = [
|
||||
|
||||
@@ -72,6 +72,7 @@ describe("POST /api/event-types", () => {
|
||||
},
|
||||
});
|
||||
req.userId = memberUserId;
|
||||
// @ts-expect-error - Return type is wrong
|
||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
||||
|
||||
await handler(req, res);
|
||||
@@ -114,6 +115,7 @@ describe("POST /api/event-types", () => {
|
||||
req.isSystemWideAdmin = true;
|
||||
req.userId = adminUserId;
|
||||
|
||||
// @ts-expect-error - Return type is wrong
|
||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
||||
|
||||
await handler(req, res);
|
||||
@@ -138,6 +140,7 @@ describe("POST /api/event-types", () => {
|
||||
req.isSystemWideAdmin = true;
|
||||
req.userId = adminUserId;
|
||||
|
||||
// @ts-expect-error - Return type is wrong
|
||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => true);
|
||||
vi.mocked(checkParentEventOwnership).mockImplementationOnce(async () => undefined);
|
||||
vi.mocked(checkUserMembership).mockImplementationOnce(async () => undefined);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import type { ILicenseKeyService } from "@calcom/ee/common/server/LicenseKeyService";
|
||||
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
||||
@@ -21,7 +22,7 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe("Verify API key", () => {
|
||||
let service: LicenseKeyService;
|
||||
let service: ILicenseKeyService;
|
||||
|
||||
beforeEach(async () => {
|
||||
service = await LicenseKeyService.create();
|
||||
|
||||
@@ -79,6 +79,15 @@ describe("POST /api/selected-calendars", () => {
|
||||
integration: "google",
|
||||
externalId: "ext123",
|
||||
userId: 444444,
|
||||
id: "xxx-xxx",
|
||||
eventTypeId: null,
|
||||
domainWideDelegationCredentialId: null,
|
||||
googleChannelId: null,
|
||||
googleChannelKind: null,
|
||||
googleChannelResourceId: null,
|
||||
googleChannelResourceUri: null,
|
||||
googleChannelExpiration: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
await handler(req, res);
|
||||
@@ -104,10 +113,19 @@ describe("POST /api/selected-calendars", () => {
|
||||
req.userId = 333333;
|
||||
|
||||
prismaMock.selectedCalendar.create.mockResolvedValue({
|
||||
id: "xxx-xxx",
|
||||
credentialId: 1,
|
||||
integration: "google",
|
||||
externalId: "ext123",
|
||||
userId: 333333,
|
||||
googleChannelId: null,
|
||||
googleChannelKind: null,
|
||||
googleChannelResourceId: null,
|
||||
googleChannelResourceUri: null,
|
||||
googleChannelExpiration: null,
|
||||
domainWideDelegationCredentialId: null,
|
||||
eventTypeId: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
@@ -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.73",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.94",
|
||||
"@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);
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
} from "@calcom/platform-constants";
|
||||
|
||||
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
||||
@@ -45,6 +46,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
|
||||
allowedHeaders: [
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
CAL_API_VERSION_HEADER,
|
||||
"Accept",
|
||||
"Authorization",
|
||||
|
||||
@@ -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,11 +33,12 @@ 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";
|
||||
|
||||
import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
|
||||
import { X_CAL_CLIENT_ID, X_CAL_PLATFORM_EMBED } from "@calcom/platform-constants";
|
||||
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
|
||||
import {
|
||||
handleNewRecurringBooking,
|
||||
@@ -161,14 +162,15 @@ export class BookingsController_2024_04_15 {
|
||||
async createBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() body: CreateBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<Partial<BookingResponse>>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
const { orgSlug, locationUrl } = body;
|
||||
req.headers["x-cal-force-slug"] = orgSlug;
|
||||
try {
|
||||
const booking = await handleNewBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl)
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed)
|
||||
);
|
||||
if (booking.userId && booking.uid && booking.startTime) {
|
||||
void (await this.billingService.increaseUsageByUserId(booking.userId, {
|
||||
@@ -192,13 +194,16 @@ export class BookingsController_2024_04_15 {
|
||||
@Req() req: BookingRequest,
|
||||
@Param("bookingId") bookingId: string,
|
||||
@Body() _: CancelBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
if (bookingId) {
|
||||
try {
|
||||
req.body.id = parseInt(bookingId);
|
||||
const res = await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
|
||||
const res = await handleCancelBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
if (!res.onlyRemovedAttendee) {
|
||||
void (await this.billingService.cancelUsageByBookingUid(res.bookingUid));
|
||||
}
|
||||
@@ -246,7 +251,8 @@ export class BookingsController_2024_04_15 {
|
||||
async createRecurringBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() _: CreateRecurringBookingInput_2024_04_15[],
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<BookingResponse[]>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
try {
|
||||
@@ -258,7 +264,7 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
|
||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId)
|
||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
|
||||
createdBookings.forEach(async (booking) => {
|
||||
@@ -284,13 +290,14 @@ export class BookingsController_2024_04_15 {
|
||||
async createInstantBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Body() _: CreateBookingInput_2024_04_15,
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
||||
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
|
||||
const oAuthClientId = clientId?.toString();
|
||||
req.userId = (await this.getOwnerId(req)) ?? -1;
|
||||
try {
|
||||
const instantMeeting = await handleInstantMeeting(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId)
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
|
||||
if (instantMeeting.userId && instantMeeting.bookingUid) {
|
||||
@@ -332,8 +339,14 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
}
|
||||
|
||||
private async getOAuthClientsParams(clientId: string): Promise<OAuthRequestParams> {
|
||||
private async getOAuthClientsParams(clientId: string, isEmbed = false): Promise<OAuthRequestParams> {
|
||||
const res = { ...DEFAULT_PLATFORM_PARAMS };
|
||||
|
||||
if (isEmbed) {
|
||||
// embed should ignore oauth client settings and enable emails by default
|
||||
return { ...res, arePlatformEmailsEnabled: true };
|
||||
}
|
||||
|
||||
try {
|
||||
const client = await this.oAuthClientRepository.getOAuthClient(clientId);
|
||||
// fetch oAuthClient from db and use data stored in db to set these values
|
||||
@@ -354,13 +367,14 @@ export class BookingsController_2024_04_15 {
|
||||
private async createNextApiBookingRequest(
|
||||
req: BookingRequest,
|
||||
oAuthClientId?: string,
|
||||
platformBookingLocation?: string
|
||||
platformBookingLocation?: string,
|
||||
isEmbed?: string
|
||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||
const requestId = req.get("X-Request-Id");
|
||||
const clone = { ...req };
|
||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||
const oAuthParams = oAuthClientId
|
||||
? await this.getOAuthClientsParams(oAuthClientId)
|
||||
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||
: DEFAULT_PLATFORM_PARAMS;
|
||||
this.logger.log(`createNextApiBookingRequest_2024_04_15`, {
|
||||
requestId,
|
||||
@@ -370,19 +384,24 @@ 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;
|
||||
}
|
||||
|
||||
private async createNextApiRecurringBookingRequest(
|
||||
req: BookingRequest,
|
||||
oAuthClientId?: string,
|
||||
platformBookingLocation?: string
|
||||
platformBookingLocation?: string,
|
||||
isEmbed?: string
|
||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||
const clone = { ...req };
|
||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||
const oAuthParams = oAuthClientId
|
||||
? await this.getOAuthClientsParams(oAuthClientId)
|
||||
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||
: DEFAULT_PLATFORM_PARAMS;
|
||||
const requestId = req.get("X-Request-Id");
|
||||
this.logger.log(`createNextApiRecurringBookingRequest_2024_04_15`, {
|
||||
@@ -397,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;
|
||||
}
|
||||
@@ -424,4 +444,8 @@ export class BookingsController_2024_04_15 {
|
||||
|
||||
throw new InternalServerErrorException(errMsg);
|
||||
}
|
||||
|
||||
private transformToBoolean(v?: string): boolean {
|
||||
return v && typeof v === "string" ? v.toLowerCase() === "true" : false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
>;
|
||||
}
|
||||
|
||||
@@ -32,5 +32,6 @@ import { Module } from "@nestjs/common";
|
||||
ApiKeyRepository,
|
||||
],
|
||||
controllers: [BookingsController_2024_08_13],
|
||||
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
|
||||
})
|
||||
export class BookingsModule_2024_08_13 {}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { MarkAbsentBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/out
|
||||
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
||||
import { VERSION_2024_08_13_VALUE } from "@/lib/api-versions";
|
||||
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
@ApiHeader({
|
||||
name: "cal-api-version",
|
||||
description: `Must be set to \`2024-08-13\``,
|
||||
example: VERSION_2024_08_13,
|
||||
required: true,
|
||||
})
|
||||
export class BookingsController_2024_08_13 {
|
||||
|
||||
+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 },
|
||||
},
|
||||
|
||||
+53
-46
@@ -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,10 +950,13 @@ 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);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
const firstAttendee = firstBooking.attendees.find(
|
||||
(attendee) => attendee.name === nameAttendeeOne
|
||||
);
|
||||
expect(firstAttendee).toEqual({
|
||||
name: nameAttendeeOne,
|
||||
email: emailAttendeeOne,
|
||||
timeZone: body.attendee.timeZone,
|
||||
@@ -965,7 +969,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
},
|
||||
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
|
||||
});
|
||||
expect(firstBooking.attendees[1]).toEqual({
|
||||
const secondAttendee = firstBooking.attendees.find(
|
||||
(attendee) => attendee.name === nameAttendeeTwo
|
||||
);
|
||||
expect(secondAttendee).toEqual({
|
||||
name: nameAttendeeTwo,
|
||||
email: emailAttendeeTwo,
|
||||
timeZone: body.attendee.timeZone,
|
||||
@@ -997,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);
|
||||
});
|
||||
});
|
||||
@@ -422,6 +561,43 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should get bookings by teamId and eventTypeId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamId=${team2.id}&eventTypeId=${team2EventTypeId}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
const data: (
|
||||
| BookingOutput_2024_08_13
|
||||
| RecurringBookingOutput_2024_08_13
|
||||
| GetSeatedBookingOutput_2024_08_13
|
||||
)[] = responseBody.data;
|
||||
expect(data.length).toEqual(1);
|
||||
expect(data[0].eventTypeId).toEqual(team2EventTypeId);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not get bookings by teamId and non existing eventTypeId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamId=${team2.id}&eventTypeId=90909`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
const data: (
|
||||
| BookingOutput_2024_08_13
|
||||
| RecurringBookingOutput_2024_08_13
|
||||
| GetSeatedBookingOutput_2024_08_13
|
||||
)[] = responseBody.data;
|
||||
expect(data.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings by teamIds", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?teamIds=${team1.id},${team2.id}`)
|
||||
@@ -436,7 +612,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;
|
||||
|
||||
@@ -19,7 +19,10 @@ import { DateTime } from "luxon";
|
||||
import { z } from "zod";
|
||||
|
||||
import { APPS_TYPE_ID_MAPPING } from "@calcom/platform-constants";
|
||||
import { getConnectedDestinationCalendars, getBusyCalendarTimes } from "@calcom/platform-libraries";
|
||||
import {
|
||||
getConnectedDestinationCalendarsAndEnsureDefaultsInDb,
|
||||
getBusyCalendarTimes,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { Calendar } from "@calcom/platform-types";
|
||||
import { PrismaClient } from "@calcom/prisma";
|
||||
|
||||
@@ -41,12 +44,18 @@ export class CalendarsService {
|
||||
if (!userWithCalendars) {
|
||||
throw new NotFoundException("User not found");
|
||||
}
|
||||
|
||||
return getConnectedDestinationCalendars(
|
||||
userWithCalendars,
|
||||
false,
|
||||
this.dbWrite.prisma as unknown as PrismaClient
|
||||
);
|
||||
return getConnectedDestinationCalendarsAndEnsureDefaultsInDb({
|
||||
user: {
|
||||
...userWithCalendars,
|
||||
allSelectedCalendars: userWithCalendars.selectedCalendars,
|
||||
userLevelSelectedCalendars: userWithCalendars.selectedCalendars.filter(
|
||||
(calendar) => !calendar.eventTypeId
|
||||
),
|
||||
},
|
||||
onboarding: false,
|
||||
eventTypeId: null,
|
||||
prisma: this.dbWrite.prisma as unknown as PrismaClient,
|
||||
});
|
||||
}
|
||||
|
||||
async getBusyTimes(
|
||||
|
||||
+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 })
|
||||
|
||||
+246
-77
@@ -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";
|
||||
@@ -33,11 +34,18 @@ import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
NameFieldInput_2024_06_14,
|
||||
NameDefaultFieldInput_2024_06_14,
|
||||
NotesDefaultFieldInput_2024_06_14,
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
const orderBySlug = (a: { slug: string }, b: { slug: string }) => {
|
||||
if (a.slug < b.slug) return -1;
|
||||
if (a.slug > b.slug) return 1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
describe("Event types Endpoints", () => {
|
||||
describe("Not authenticated", () => {
|
||||
let app: INestApplication;
|
||||
@@ -79,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;
|
||||
@@ -93,6 +101,67 @@ describe("Event types Endpoints", () => {
|
||||
let orgUserEventType1: EventType;
|
||||
let orgUserEventType2: EventType;
|
||||
let orgUserEventType3: EventType;
|
||||
|
||||
const defaultResponseBookingFieldName = {
|
||||
isDefault: true,
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldEmail = {
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldLocation = {
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldTitle = {
|
||||
isDefault: true,
|
||||
type: "text",
|
||||
slug: "title",
|
||||
required: true,
|
||||
hidden: true,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldNotes = {
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "notes",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldGuests = {
|
||||
isDefault: true,
|
||||
type: "multiemail",
|
||||
slug: "guests",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
const defaultResponseBookingFieldRescheduleReason = {
|
||||
isDefault: true,
|
||||
type: "textarea",
|
||||
slug: "rescheduleReason",
|
||||
required: false,
|
||||
hidden: false,
|
||||
disableOnPrefill: false,
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
@@ -118,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({
|
||||
@@ -129,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({
|
||||
@@ -150,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
|
||||
);
|
||||
|
||||
@@ -173,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",
|
||||
});
|
||||
|
||||
@@ -253,7 +337,7 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should create an event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
@@ -291,6 +375,7 @@ describe("Event types Endpoints", () => {
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
scheduleId: firstSchedule.id,
|
||||
@@ -378,16 +463,25 @@ describe("Event types Endpoints", () => {
|
||||
);
|
||||
expect(createdEventType.color).toEqual(body.color);
|
||||
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
// note(Lauris): location booking field is added if multiple locations are passed
|
||||
{ isDefault: true, required: false, slug: "location", type: "radioInput" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldNotes },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
];
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -417,7 +511,9 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.bookingFields.sort(orderBySlug)).toEqual(
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
@@ -440,7 +536,6 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it(`/GET/event-types by username and orgSlug`, async () => {
|
||||
console.log(organization);
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/api/v2/event-types?username=${orgUser.username}&orgSlug=${organization.slug}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
|
||||
@@ -621,7 +716,6 @@ describe("Event types Endpoints", () => {
|
||||
.expect(201);
|
||||
|
||||
const createdEventType = createResponse.body.data;
|
||||
console.log("createdEventType: ", createdEventType);
|
||||
|
||||
expect(createdEventType).toMatchObject({
|
||||
id: expect.any(Number),
|
||||
@@ -764,13 +858,20 @@ describe("Event types Endpoints", () => {
|
||||
});
|
||||
|
||||
it("should update event type", async () => {
|
||||
const nameBookingField: NameFieldInput_2024_06_14 = {
|
||||
const nameBookingField: NameDefaultFieldInput_2024_06_14 = {
|
||||
type: "name",
|
||||
label: "Your name sir / madam",
|
||||
placeholder: "john doe",
|
||||
disableOnPrefill: true,
|
||||
};
|
||||
|
||||
const notesBookingField: NotesDefaultFieldInput_2024_06_14 = {
|
||||
slug: "notes",
|
||||
label: "lemme take some notes",
|
||||
placeholder: "write your notes here",
|
||||
required: true,
|
||||
};
|
||||
|
||||
const newTitle = "Coding class in Italian!";
|
||||
|
||||
const body: UpdateEventTypeInput_2024_06_14 = {
|
||||
@@ -787,7 +888,9 @@ describe("Event types Endpoints", () => {
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
notesBookingField,
|
||||
],
|
||||
bookingLimitsCount: {
|
||||
day: 4,
|
||||
@@ -850,14 +953,25 @@ describe("Event types Endpoints", () => {
|
||||
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(updatedEventType.locations).toEqual(eventType.locations);
|
||||
|
||||
const requestBookingFields = body.bookingFields || [];
|
||||
const expectedBookingFields = [
|
||||
{ isDefault: true, required: true, slug: "name", ...nameBookingField },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
...requestBookingFields
|
||||
.filter((field) => field.type !== "name" && field.type !== "email")
|
||||
.map((field) => ({ isDefault: false, ...field })),
|
||||
{ ...defaultResponseBookingFieldName, ...nameBookingField },
|
||||
{ ...defaultResponseBookingFieldEmail },
|
||||
{ ...defaultResponseBookingFieldLocation },
|
||||
{
|
||||
type: "select",
|
||||
label: "select which language you want to learn",
|
||||
slug: "select-language",
|
||||
required: true,
|
||||
placeholder: "select language",
|
||||
options: ["javascript", "python", "cobol"],
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldNotes, ...notesBookingField },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
{ ...defaultResponseBookingFieldRescheduleReason },
|
||||
];
|
||||
|
||||
expect(updatedEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
@@ -974,7 +1088,9 @@ describe("Event types Endpoints", () => {
|
||||
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
|
||||
expect(fetchedEventType?.lengthInMinutesOptions).toEqual(eventType.lengthInMinutesOptions);
|
||||
expect(fetchedEventType?.locations).toEqual(eventType.locations);
|
||||
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
|
||||
expect(fetchedEventType?.bookingFields.sort(orderBySlug)).toEqual(
|
||||
eventType.bookingFields.sort(orderBySlug).filter((f) => ("hidden" in f ? !f?.hidden : true))
|
||||
);
|
||||
expect(fetchedEventType?.ownerId).toEqual(user.id);
|
||||
expect(fetchedEventType.bookingLimitsCount).toEqual(eventType.bookingLimitsCount);
|
||||
expect(fetchedEventType.onlyShowFirstAvailableSlot).toEqual(eventType.onlyShowFirstAvailableSlot);
|
||||
@@ -1055,14 +1171,48 @@ describe("Event types Endpoints", () => {
|
||||
let legacyEventTypeId2: number;
|
||||
|
||||
const expectedReturnSystemFields = [
|
||||
{ isDefault: true, required: true, slug: "name", type: "name" },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email" },
|
||||
{ isDefault: true, type: "radioInput", slug: "location", required: false },
|
||||
{ isDefault: true, required: true, slug: "title", type: "text" },
|
||||
{ isDefault: true, required: false, slug: "notes", type: "textarea" },
|
||||
{ isDefault: true, required: false, slug: "guests", type: "multiemail" },
|
||||
{ isDefault: true, required: false, slug: "rescheduleReason", type: "textarea" },
|
||||
{ isDefault: true, type: "phone", slug: "attendeePhoneNumber", required: false },
|
||||
{ isDefault: true, required: true, slug: "name", type: "name", disableOnPrefill: false },
|
||||
{ isDefault: true, required: true, slug: "email", type: "email", disableOnPrefill: false },
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: false,
|
||||
},
|
||||
{ isDefault: true, required: true, slug: "title", type: "text", disableOnPrefill: false, hidden: true },
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "guests",
|
||||
type: "multiemail",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "rescheduleReason",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
disableOnPrefill: false,
|
||||
isDefault: true,
|
||||
type: "phone",
|
||||
slug: "attendeePhoneNumber",
|
||||
required: false,
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
required: false,
|
||||
slug: "notes",
|
||||
type: "textarea",
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
];
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -1087,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,
|
||||
@@ -1186,15 +1338,6 @@ describe("Event types Endpoints", () => {
|
||||
defaultLabel: "what_is_this_meeting_about",
|
||||
defaultPlaceholder: "",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
{
|
||||
name: "guests",
|
||||
type: "multiemail",
|
||||
@@ -1230,6 +1373,15 @@ describe("Event types Endpoints", () => {
|
||||
required: false,
|
||||
defaultLabel: "phone_number",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
],
|
||||
};
|
||||
const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id);
|
||||
@@ -1316,15 +1468,6 @@ describe("Event types Endpoints", () => {
|
||||
defaultLabel: "what_is_this_meeting_about",
|
||||
defaultPlaceholder: "",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
{
|
||||
name: "guests",
|
||||
type: "multiemail",
|
||||
@@ -1360,6 +1503,15 @@ describe("Event types Endpoints", () => {
|
||||
required: false,
|
||||
defaultLabel: "phone_number",
|
||||
},
|
||||
{
|
||||
name: "notes",
|
||||
type: "textarea",
|
||||
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||
editable: "system-but-optional",
|
||||
required: false,
|
||||
defaultLabel: "additional_notes",
|
||||
defaultPlaceholder: "share_additional_notes",
|
||||
},
|
||||
],
|
||||
};
|
||||
const legacyEventType = await eventTypesRepositoryFixture.create(legacyEventTypeInput, user.id);
|
||||
@@ -1373,17 +1525,21 @@ describe("Event types Endpoints", () => {
|
||||
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
|
||||
const fetchedEventType = responseBody.data;
|
||||
|
||||
expect(fetchedEventType.bookingFields).toEqual([
|
||||
...expectedReturnSystemFields,
|
||||
{
|
||||
isDefault: false,
|
||||
type: userDefinedBookingField.type,
|
||||
slug: userDefinedBookingField.name,
|
||||
label: userDefinedBookingField.label,
|
||||
required: userDefinedBookingField.required,
|
||||
placeholder: userDefinedBookingField.placeholder,
|
||||
},
|
||||
]);
|
||||
expect(fetchedEventType.bookingFields.sort(orderBySlug)).toEqual(
|
||||
[
|
||||
{
|
||||
isDefault: false,
|
||||
type: userDefinedBookingField.type,
|
||||
slug: userDefinedBookingField.name,
|
||||
label: userDefinedBookingField.label,
|
||||
required: userDefinedBookingField.required,
|
||||
placeholder: userDefinedBookingField.placeholder,
|
||||
disableOnPrefill: false,
|
||||
hidden: false,
|
||||
},
|
||||
...expectedReturnSystemFields,
|
||||
].sort(orderBySlug)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1461,42 +1617,53 @@ describe("Event types Endpoints", () => {
|
||||
type: "name",
|
||||
slug: "name",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "email",
|
||||
slug: "email",
|
||||
required: true,
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
isDefault: true,
|
||||
type: "radioInput",
|
||||
slug: "location",
|
||||
required: false,
|
||||
hidden: 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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -1559,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,
|
||||
|
||||
+18
-1
@@ -34,6 +34,7 @@ import {
|
||||
UpdateEventTypeInput_2024_06_14,
|
||||
GetEventTypesQuery_2024_06_14,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
@@ -113,10 +114,26 @@ export class EventTypesController_2024_06_14 {
|
||||
@Query() queryParams: GetEventTypesQuery_2024_06_14
|
||||
): Promise<GetEventTypesOutput_2024_06_14> {
|
||||
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
|
||||
const eventTypesFormatted = this.eventTypeResponseTransformPipe.transform(eventTypes);
|
||||
const eventTypesWithoutHiddenFields = eventTypesFormatted.map((eventType) => {
|
||||
return {
|
||||
...eventType,
|
||||
bookingFields: Array.isArray(eventType?.bookingFields)
|
||||
? eventType?.bookingFields
|
||||
.map((field) => {
|
||||
if ("hidden" in field) {
|
||||
return field.hidden !== true ? field : null;
|
||||
}
|
||||
return field;
|
||||
})
|
||||
.filter((f) => f)
|
||||
: [],
|
||||
};
|
||||
}) as EventTypeOutput_2024_06_14[];
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: this.eventTypeResponseTransformPipe.transform(eventTypes),
|
||||
data: eventTypesWithoutHiddenFields,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+36
-14
@@ -12,9 +12,12 @@ import {
|
||||
transformRecurrenceApiToInternal,
|
||||
systemBeforeFieldName,
|
||||
systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
systemAfterFieldTitle,
|
||||
systemAfterFieldNotes,
|
||||
systemAfterFieldGuests,
|
||||
systemAfterFieldRescheduleReason,
|
||||
EventTypeMetaDataSchema,
|
||||
systemBeforeFieldLocation,
|
||||
transformBookerLayoutsApiToInternal,
|
||||
transformConfirmationPolicyApiToInternal,
|
||||
transformEventColorsApiToInternal,
|
||||
@@ -217,27 +220,46 @@ export class InputEventTypesService_2024_06_14 {
|
||||
inputBookingFields: CreateEventTypeInput_2024_06_14["bookingFields"],
|
||||
hasMultipleLocations: boolean
|
||||
) {
|
||||
const customFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
const internalFields: (SystemField | CustomField)[] = inputBookingFields
|
||||
? transformBookingFieldsApiToInternal(inputBookingFields)
|
||||
: [];
|
||||
const customFieldsWithoutNameEmail = customFields.filter(
|
||||
(field) => field.type !== "name" && field.type !== "email"
|
||||
const systemCustomFields = internalFields.filter((field) => !this.isUserCustomField(field));
|
||||
const userCustomFields = internalFields.filter((field) => this.isUserCustomField(field));
|
||||
|
||||
const systemCustomNameField = systemCustomFields?.find((field) => field.type === "name");
|
||||
const systemCustomEmailField = systemCustomFields?.find((field) => field.type === "email");
|
||||
const systemCustomTitleField = systemCustomFields?.find((field) => field.name === "title");
|
||||
const systemCustomNotesField = systemCustomFields?.find((field) => field.name === "notes");
|
||||
const systemCustomGuestsField = systemCustomFields?.find((field) => field.name === "guests");
|
||||
const systemCustomRescheduleReasonField = systemCustomFields?.find(
|
||||
(field) => field.name === "rescheduleReason"
|
||||
);
|
||||
const customNameField = customFields?.find((field) => field.type === "name");
|
||||
const customEmailField = customFields?.find((field) => field.type === "email");
|
||||
|
||||
const defaultFieldsBefore: (SystemField | CustomField)[] = [
|
||||
customNameField || systemBeforeFieldName,
|
||||
customEmailField || systemBeforeFieldEmail,
|
||||
systemCustomNameField || systemBeforeFieldName,
|
||||
systemCustomEmailField || systemBeforeFieldEmail,
|
||||
systemBeforeFieldLocation,
|
||||
];
|
||||
// note(Lauris): if event type has multiple locations then a radio button booking field has to be displayed to allow booker to pick location
|
||||
if (hasMultipleLocations) {
|
||||
defaultFieldsBefore.push(systemBeforeFieldLocation);
|
||||
}
|
||||
|
||||
const defaultFieldsAfter = [systemAfterFieldRescheduleReason];
|
||||
const defaultFieldsAfter = [
|
||||
systemCustomTitleField || systemAfterFieldTitle,
|
||||
systemCustomNotesField || systemAfterFieldNotes,
|
||||
systemCustomGuestsField || systemAfterFieldGuests,
|
||||
systemCustomRescheduleReasonField || systemAfterFieldRescheduleReason,
|
||||
];
|
||||
|
||||
return [...defaultFieldsBefore, ...customFieldsWithoutNameEmail, ...defaultFieldsAfter];
|
||||
return [...defaultFieldsBefore, ...userCustomFields, ...defaultFieldsAfter];
|
||||
}
|
||||
|
||||
isUserCustomField(field: SystemField | CustomField): field is CustomField {
|
||||
return (
|
||||
field.type !== "name" &&
|
||||
field.type !== "email" &&
|
||||
field.name !== "title" &&
|
||||
field.name !== "notes" &&
|
||||
field.name !== "guests" &&
|
||||
field.name !== "rescheduleReason"
|
||||
);
|
||||
}
|
||||
|
||||
transformInputIntervalLimits(inputBookingFields: CreateEventTypeInput_2024_06_14["bookingLimitsCount"]) {
|
||||
|
||||
+1
@@ -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 || []);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -10,8 +10,8 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/
|
||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||
import { SlotsModule } from "@/modules/slots/slots.module";
|
||||
import { TeamsEventTypesModule } from "@/modules/teams/event-types/teams-event-types.module";
|
||||
import { TeamsModule } from "@/modules/teams/teams/teams.module";
|
||||
import { TeamsMembershipsModule } from "@/modules/teams/memberships/teams-memberships.module";
|
||||
import { TeamsModule } from "@/modules/teams/teams/teams.module";
|
||||
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
|
||||
+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,
|
||||
};
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ export class OutputSchedulesService_2024_06_11 {
|
||||
|
||||
async getResponseSchedule(databaseSchedule: Schedule & { availability: Availability[] }) {
|
||||
if (!databaseSchedule.timeZone) {
|
||||
throw new Error("Failed to create schedule because its timezone is not set.");
|
||||
databaseSchedule.timeZone = "Europe/London";
|
||||
}
|
||||
|
||||
const ownerDefaultScheduleId = await this.usersRepository.getUserScheduleDefaultId(
|
||||
|
||||
@@ -41,6 +41,18 @@ describe("isOriginAllowed", () => {
|
||||
const origin = "https://sub.mydomain.com/path/to/resource";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle patterns with wildcard for routes correctly", () => {
|
||||
const allowedOrigins = ["https://domain.com/*"];
|
||||
const origin = "https://domain.com/dashboard";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
|
||||
it("should handle patterns with wildcard for root route correctly", () => {
|
||||
const allowedOrigins = ["https://domain.com*"];
|
||||
const origin = "https://domain.com";
|
||||
expect(isOriginAllowed(origin, allowedOrigins)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("is not allowed", () => {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -179,7 +179,7 @@ export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth")
|
||||
|
||||
if (origin && !isOriginAllowed(origin, client.redirectUris)) {
|
||||
throw new UnauthorizedException(
|
||||
`Invalid request origin - please open https://app.cal.com/settings/platform and add the origin '${origin}' to the 'Redirect uris' of your OAuth client.`
|
||||
`Invalid request origin - please open https://app.cal.com/settings/platform and add the origin '${origin}' to the 'Redirect uris' of your OAuth client with ID '${client.id}'`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user