Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af524d9e93 | ||
|
|
6b7ced262d | ||
|
|
7a797490c8 | ||
|
|
3c367dd2c8 | ||
|
|
00f50a9449 | ||
|
|
381e22ae7e | ||
|
|
3850b519fd | ||
|
|
78473bab1b | ||
|
|
e437dfa000 | ||
|
|
07789ba697 | ||
|
|
a6c6a46076 | ||
|
|
d44d74d2fc | ||
|
|
52662b3375 | ||
|
|
8612fa71c7 | ||
|
|
caaecf2f94 | ||
|
|
8741714402 | ||
|
|
271d9a5f61 | ||
|
|
3d3acba82a | ||
|
|
ca1f84c827 | ||
|
|
a44bcafcf2 | ||
|
|
e4ab8d87c4 | ||
|
|
a4519e705d | ||
|
|
a3113607ff | ||
|
|
acf270bc61 | ||
|
|
d1a9af84b2 | ||
|
|
6ea879f265 | ||
|
|
307b2946a2 | ||
|
|
23d9788dce | ||
|
|
caade0850a | ||
|
|
f6af6e34bd | ||
|
|
5f5cfaf7c7 | ||
|
|
07a4aa6c7d | ||
|
|
641193bd61 | ||
|
|
2d372746d3 | ||
|
|
4b2265c35d | ||
|
|
4dc2d3236d | ||
|
+20 |
7bff15cc47 | ||
|
|
97ef32f7ca | ||
|
|
16a94fe712 | ||
|
|
8f29811d5a | ||
|
|
a47cd5b106 | ||
|
|
64aa19fcea | ||
|
|
7c71b179c8 | ||
|
|
d485e5f621 | ||
|
|
aa8d0f78d6 | ||
|
|
564497ec2a | ||
|
|
2089ad40ea | ||
|
|
42e94cd5b7 | ||
|
|
4546bd8653 | ||
|
|
2a89a5e249 | ||
|
|
aba7c6df7f | ||
|
|
97348e2052 | ||
|
|
30149dd100 | ||
|
|
d91ccd37e0 | ||
|
|
0bd90d1673 | ||
|
|
6899b853ee | ||
|
|
b07d81d0b9 | ||
|
|
22104f8d91 | ||
|
|
26d2decea4 | ||
|
|
b6d8dc0bd6 | ||
|
|
7c0cdcfc6d | ||
|
|
ad23613599 | ||
|
|
71598a4389 | ||
|
|
2ce1828841 | ||
|
|
b287f7e047 | ||
|
|
c2be796a5b | ||
|
|
829bb63eb9 | ||
|
|
07accf700e | ||
|
|
46f73e79c1 | ||
|
|
b0921fe90a | ||
|
|
3c956dd8e8 | ||
|
|
197a7ba6e9 | ||
|
|
e6daed762b | ||
|
|
0b3aa4fd34 | ||
|
|
4a6704c934 | ||
|
|
fbcfc35621 | ||
|
|
0d2ec20f66 | ||
|
|
01fc501cb9 | ||
|
|
1be002c33c | ||
|
|
8a4b03833d | ||
|
|
8714dda047 | ||
|
|
5b89123307 | ||
|
|
3221379584 | ||
|
|
578f5dc16b | ||
|
|
3f042a1077 | ||
|
|
a44e7b2371 | ||
|
|
b06a94ab57 | ||
|
|
ec55454e43 | ||
|
|
9ec0217858 | ||
|
|
66d69f5a06 | ||
|
|
b7b58c2dc7 | ||
|
|
de1e19ee6d | ||
|
|
a366cb7c89 | ||
|
|
dac336894c | ||
|
|
373c17a46d | ||
|
|
4c00c1ac2b | ||
|
|
050a030ff0 | ||
|
|
dfb2232a59 | ||
|
|
984ccf7c80 | ||
|
|
e87168322c | ||
|
|
bf9e9b4ae1 | ||
|
|
6d4b05cf1d | ||
|
|
bd1af4027f | ||
|
|
3872175393 | ||
|
|
5ca063d4e8 | ||
|
|
112aa8201f | ||
|
|
17fcc2c213 | ||
|
|
d3f1dc1b32 | ||
|
|
25f3368fa1 | ||
|
|
9a271f397b | ||
|
|
6847781a7f | ||
|
|
2d9a3bd741 | ||
|
|
649faf4ff6 | ||
|
|
03346f04ed | ||
|
|
99260974f4 | ||
|
|
4a493c0a0e | ||
|
|
47435c36ff | ||
|
|
599744b31c | ||
|
|
d2c4c45137 | ||
|
|
43ba47a122 | ||
|
|
0048b2d92c | ||
|
|
9dc0570d38 | ||
|
|
1fbfca958a | ||
|
|
7a6c93795a | ||
|
|
4481b232cc | ||
|
|
84b265cd54 | ||
|
|
eb86064995 | ||
|
|
39fdf5a74e | ||
|
|
f28c605b50 | ||
|
|
63d26c4fba | ||
|
|
14bba343a4 | ||
|
|
e441f3308f | ||
|
|
b4aad453cd | ||
|
|
dc7a1c4680 | ||
|
|
fd651133c2 | ||
|
|
f0d2a1a051 | ||
|
|
62c7627775 | ||
|
|
be53a20429 | ||
|
|
5c6f52f1d4 | ||
|
|
0e9e3e5012 | ||
|
|
5d709cf63c | ||
|
|
0f67faeef3 | ||
|
|
1ff4f557f6 | ||
|
|
7e2c0bd29e | ||
|
|
5b59cf9d9c | ||
|
|
d5420eda00 | ||
|
|
c17ac67b3f | ||
|
|
b2763a6274 | ||
|
|
08b6f60407 | ||
|
|
466b3e4c08 | ||
|
|
401209810a | ||
|
|
9f34a840d0 | ||
|
|
37f1535e1a | ||
|
|
5dc81e8677 | ||
|
|
5a166ee430 | ||
|
|
f6f964d495 | ||
|
|
6364e85293 | ||
|
|
46add2bebc | ||
|
|
8ada940dc7 | ||
|
|
d027f8004a | ||
|
|
cc2f16411b | ||
|
|
15b58c1c13 | ||
|
|
2d12ea7372 | ||
|
|
06bf9cfbfe | ||
|
|
5dbc6d9ca6 | ||
|
|
486e3a386e | ||
|
|
3a29327761 | ||
|
|
4f226cd653 | ||
|
|
207804bd97 | ||
|
|
7969789d60 | ||
|
|
67fb0e6719 | ||
|
|
7f390b147a | ||
|
|
1dc869971e | ||
|
|
3fe83bb45c | ||
|
|
4fbeff31e7 | ||
|
|
e800341485 | ||
|
|
f5dc22f145 | ||
|
|
4fd31db6ee | ||
|
|
f58d3cf837 | ||
|
|
fc79ea898a | ||
|
|
5a602f10e6 | ||
|
|
e29b42a02b | ||
|
|
cdb57eaa59 | ||
|
|
662ba8dfc6 | ||
|
|
552d88ad6d | ||
|
|
08bcc77e2c | ||
|
|
c211f181fc | ||
|
|
cc6016aef3 | ||
|
|
77d0fa9ff8 | ||
|
|
720136fcf9 | ||
|
|
9bf3732717 | ||
|
|
cd721308d0 | ||
|
|
74c292cc57 | ||
|
|
8e26882b2d | ||
|
|
a775bec902 | ||
|
|
75ff5f6fb1 | ||
|
|
7e20b74674 | ||
|
|
111f292c0d | ||
|
|
52775dd27c | ||
|
|
668304a7cd | ||
|
|
378917ba19 | ||
|
|
15334ff20b | ||
|
|
76c09ca7bf | ||
|
|
0fb0974769 | ||
|
|
2840d64197 | ||
|
|
77a00b3e8c | ||
|
|
cd04b17113 | ||
|
|
cbf2604029 | ||
|
|
403cfebb89 | ||
|
|
5d2c8a49ba | ||
|
|
87f02f5588 | ||
|
|
4671ffc4f4 | ||
|
|
6cad7ac1d1 | ||
|
|
aff2c7cdb4 | ||
|
|
787ff543df | ||
|
|
01e442df17 | ||
|
|
808de26401 | ||
|
|
6ca76707da | ||
|
|
e79af63e6c | ||
|
|
24a080b988 | ||
|
|
8612f77c07 | ||
|
|
1babf5bf50 | ||
|
|
e8890156a1 | ||
|
|
c9bfd977b1 | ||
|
|
19921ab7c9 | ||
|
|
239c8a6fa9 | ||
|
|
cb9dfaa76b | ||
|
|
0b46415856 | ||
|
|
3a522320e7 | ||
|
|
89033c0e9c | ||
|
|
3972972d57 | ||
|
|
695d2c0f39 | ||
|
|
4c32d0c499 | ||
|
|
3252efebb1 | ||
|
|
a6fa9d7d60 | ||
|
|
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 |
+18
-13
@@ -112,7 +112,7 @@ NEXT_PUBLIC_POSTHOG_HOST=
|
||||
PLAIN_API_KEY=
|
||||
PLAIN_API_URL=https://api.plain.com/v1
|
||||
PLAIN_HMAC_SECRET_KEY=
|
||||
PLAIN_CHAT_ID=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_ID=
|
||||
PLAIN_CHAT_HMAC_SECRET_KEY=
|
||||
NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS=
|
||||
|
||||
@@ -242,6 +242,7 @@ E2E_TEST_MAILHOG_ENABLED=
|
||||
|
||||
# Cloudflare Turnstile
|
||||
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
||||
NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=
|
||||
CLOUDFLARE_TURNSTILE_SECRET=
|
||||
|
||||
# Set the following value to true if you wish to enable Team Impersonation
|
||||
@@ -355,21 +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
|
||||
APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0
|
||||
APP_ROUTER_APPS_SLUG_ENABLED=0
|
||||
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
||||
# whether we redirect to the future/apps/categories from /apps/categories or not
|
||||
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
||||
APP_ROUTER_APPS_ENABLED=0
|
||||
APP_ROUTER_TEAM_ENABLED=0
|
||||
APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGIN_ENABLED=0
|
||||
APP_ROUTER_AUTH_LOGOUT_ENABLED=0
|
||||
APP_ROUTER_AUTH_SAML_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
|
||||
@@ -399,6 +388,12 @@ BLACKLISTED_GUEST_EMAILS=
|
||||
NEXT_PUBLIC_VAPID_PUBLIC_KEY=
|
||||
VAPID_PRIVATE_KEY=
|
||||
|
||||
# Mintlify chat api
|
||||
# Power AI chat in for docs
|
||||
NEXT_PUBLIC_MINTLIFY_CHAT_API_KEY=
|
||||
NEXT_PUBLIC_CHAT_API_URL=
|
||||
NEXT_PUBLIC_DOCS_URL=
|
||||
|
||||
# Custom privacy policy / terms URLs (for self-hosters: change to your privacy policy / terms URLs)
|
||||
NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL=
|
||||
NEXT_PUBLIC_WEBSITE_TERMS_URL=
|
||||
@@ -417,3 +412,13 @@ 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=
|
||||
|
||||
## Env variables related to avoiding booking failures
|
||||
# Request for checking reservation would be attempted to send every these seconds if the request is stale at that time
|
||||
NEXT_PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS=
|
||||
# Request for checking reservation of same slot would be sent only after this time has passed. It must be lower than PUBLIC_QUERY_RESERVATION_INTERVAL_SECONDS
|
||||
NEXT_PUBLIC_QUERY_RESERVATION_STALE_TIME_SECONDS=
|
||||
# Query available slots interval - Should be kept high in minutes as it could cause significant load on the system
|
||||
NEXT_PUBLIC_QUERY_AVAILABLE_SLOTS_INTERVAL_SECONDS=
|
||||
# Used to invalidate available slots when navigating to booking form
|
||||
NEXT_PUBLIC_INVALIDATE_AVAILABLE_SLOTS_ON_BOOKING_FORM=0
|
||||
@@ -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,12 @@
|
||||
/packages/app-store/larkcalendar/**/* @calcom/Foundation
|
||||
/packages/app-store/office365calendar/**/* @calcom/Foundation
|
||||
/packages/app-store/zohocalendar/**/* @calcom/Foundation
|
||||
/packages/core/getAggregatedAvailability/index.ts @calcom/Foundation
|
||||
/packages/core/getAggregateWorkingHours.ts @calcom/Foundation
|
||||
/packages/core/getUserAvailability.ts @calcom/Foundation
|
||||
/packages/features/bookings/lib/**/* @calcom/Foundation
|
||||
/packages/lib/server/getLuckyUser.ts @calcom/Foundation
|
||||
/packages/lib/slots.ts @calcom/Foundation
|
||||
/packages/platform/**/* @calcom/Platform
|
||||
/packages/prisma/**/* @calcom/Foundation
|
||||
/packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation
|
||||
|
||||
@@ -5,9 +5,19 @@
|
||||
- Fixes #XXXX (GitHub issue number)
|
||||
- Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)
|
||||
|
||||
<!-- Please provide a loom video for visual changes to speed up reviews
|
||||
Loom Video: https://www.loom.com/
|
||||
-->
|
||||
## Visual Demo (For contributors especially)
|
||||
|
||||
A visual demonstration is strongly recommended, for both the original and new change **(video / image - any one)**.
|
||||
|
||||
#### Video Demo (if applicable):
|
||||
|
||||
- Show screen recordings of the issue or feature.
|
||||
- Demonstrate how to reproduce the issue, the behavior before and after the change.
|
||||
|
||||
#### Image Demo (if applicable):
|
||||
|
||||
- Add side-by-side screenshots of the original and updated change.
|
||||
- Highlight any significant change(s).
|
||||
|
||||
## Mandatory Tasks (DO NOT REMOVE)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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: |
|
||||
|
||||
@@ -4,6 +4,7 @@ on: # yamllint disable-line rule:truthy
|
||||
release:
|
||||
types:
|
||||
- created
|
||||
- published
|
||||
# in case manual trigger is needed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
|
||||
@@ -96,3 +96,4 @@ apps/auth
|
||||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
i18n.cache
|
||||
|
||||
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
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
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
undecided:
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
Vendored
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
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
@@ -14,7 +14,7 @@ test.describe("Org", () => {
|
||||
test("Org User(Rick) Page should be embeddable", async ({ page }) => {
|
||||
const response = await page.goto("https://i.cal.com/team-rick/embed");
|
||||
expect(response?.status()).toBe(200);
|
||||
await expect(page.locator("text=Rick in i.cal.com")).toBeVisible();
|
||||
await expect(page.locator("text=Used by Checkly")).toBeVisible();
|
||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||
});
|
||||
|
||||
@@ -58,10 +58,10 @@ test.describe("Org", () => {
|
||||
expect(response?.status()).toBe(200);
|
||||
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Group Meeting");
|
||||
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[0].name);
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
||||
// 2 users and 1 for the organization(2+1)
|
||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(
|
||||
"Join us for a meeting with multiple people"
|
||||
);
|
||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -75,7 +75,9 @@ test.describe("Org", () => {
|
||||
|
||||
test.describe("Browse the Engineering Team", async () => {
|
||||
test("By User Navigation", async ({ page }) => {
|
||||
await page.goto("https://i.cal.com");
|
||||
const response = await page.goto("https://i.cal.com");
|
||||
await page.waitForLoadState("networkidle");
|
||||
expect(response?.status()).toBe(200);
|
||||
await page.click('text="Engineering"');
|
||||
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ API_KEY_PREFIX=cal_
|
||||
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
|
||||
NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
||||
|
||||
# Get it in console.cal.com
|
||||
# Get CALCOM_LICENSE_KEY here: https://go.cal.com/get-license
|
||||
CALCOM_LICENSE_KEY=""
|
||||
NEXT_PUBLIC_API_V2_ROOT_URL=http://localhost:5555
|
||||
|
||||
NEXT_PUBLIC_API_V2_ROOT_URL=http://localhost:5555
|
||||
|
||||
@@ -190,16 +190,6 @@ We make sure of this by not using next in dev, but next build && next start, if
|
||||
See <https://github.com/vercel/next.js/blob/canary/packages/next/server/dev/hot-reloader.ts#L79>. Here in dev mode OPTIONS method is hardcoded to return only GET and OPTIONS as allowed method. Running in Production mode would cause this file to be not used. This is hot-reloading logic only.
|
||||
To remove this limitation, we need to ensure that on local endpoints are requested by swagger at /api/v1 and not /v1
|
||||
|
||||
## Hosted api through cal.com
|
||||
|
||||
> _❗ WARNING: This is still experimental and not fully implemented yet❗_
|
||||
|
||||
Go to console.cal.com
|
||||
Add a deployment or go to an existing one.
|
||||
Activate API or Admin addon
|
||||
Provide your `DATABASE_URL`
|
||||
Now you can call api.cal.com?key=CALCOM_LICENSE_KEY, which will connect to your own databaseUrl.
|
||||
|
||||
## How to deploy
|
||||
|
||||
We recommend deploying API in vercel.
|
||||
|
||||
@@ -13,6 +13,8 @@ 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(),
|
||||
}));
|
||||
@@ -26,6 +28,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
query: {},
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
@@ -38,6 +41,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
||||
@@ -50,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),
|
||||
});
|
||||
@@ -60,6 +64,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
@@ -89,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"));
|
||||
@@ -104,6 +110,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
@@ -127,8 +134,8 @@ describe("rateLimitApiKey middleware", () => {
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: "test-key",
|
||||
identifierType: "apiKey",
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
@@ -140,6 +147,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
@@ -163,8 +171,8 @@ describe("rateLimitApiKey middleware", () => {
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: "test-key",
|
||||
identifierType: "apiKey",
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
@@ -176,6 +184,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
@@ -200,8 +209,8 @@ describe("rateLimitApiKey middleware", () => {
|
||||
await rateLimitApiKey(req, res, next);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: "test-key",
|
||||
identifierType: "apiKey",
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
@@ -214,6 +223,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
// Mock checkRateLimitAndThrowError to throw HttpError
|
||||
|
||||
@@ -5,12 +5,14 @@ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowE
|
||||
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: async (response) => {
|
||||
res.setHeader("X-RateLimit-Limit", response.limit);
|
||||
@@ -19,8 +21,8 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const didLock = await handleAutoLock({
|
||||
identifier: req.query.apiKey as string, // Casting as this is verified in another middleware
|
||||
identifierType: "apiKey",
|
||||
identifier,
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: response,
|
||||
});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { bookingCancelSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { bookingCancelSchema } from "~/lib/validations/booking";
|
||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,7 @@ async function handler(req: NextApiRequest) {
|
||||
...req.query,
|
||||
allRemainingBookings: req.query.allRemainingBookings === "true",
|
||||
});
|
||||
|
||||
// Normalizing for universal handler
|
||||
req.body = { id, allRemainingBookings, cancellationReason };
|
||||
return await handleCancelBooking(req);
|
||||
|
||||
@@ -5,6 +5,7 @@ import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
|
||||
@@ -213,6 +214,10 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
*/
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||
req.body = {
|
||||
...req.body,
|
||||
creationSource: CreationSource.API_V1,
|
||||
};
|
||||
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||
|
||||
if (isOrganizationOwnerOrAdmin) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||
@@ -43,6 +44,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
language: data.language,
|
||||
teamId: data.teamId,
|
||||
usernameOrEmail: data.usernameOrEmail,
|
||||
creationSource: CreationSource.API_V1,
|
||||
});
|
||||
|
||||
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||
|
||||
@@ -2,7 +2,8 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { UserCreationService } from "@calcom/lib/server/service/userCreationService";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { schemaUserCreateBodyParams } from "~/lib/validations/user";
|
||||
|
||||
@@ -92,7 +93,9 @@ 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 UserCreationService.createUser({
|
||||
data: { ...data, creationSource: CreationSource.API_V1 },
|
||||
});
|
||||
req.statusCode = 201;
|
||||
return { user };
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { Booking } from "@calcom/prisma/client";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import handler from "../../../pages/api/bookings/_post";
|
||||
|
||||
@@ -220,6 +221,34 @@ describe.skipIf(true)("POST /api/bookings", () => {
|
||||
});
|
||||
expect(previousBooking?.status).toBe("cancelled");
|
||||
});
|
||||
|
||||
test("Creates source as api_v1", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
name: "test",
|
||||
start: dayjs().format(),
|
||||
end: dayjs().add(1, "day").format(),
|
||||
eventTypeId: 2,
|
||||
email: "test@example.com",
|
||||
location: "Cal.com Video",
|
||||
timeZone: "America/Montevideo",
|
||||
language: "en",
|
||||
customInputs: [],
|
||||
metadata: {},
|
||||
userId: 4,
|
||||
},
|
||||
prisma,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType());
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
await handler(req, res);
|
||||
createdBooking = JSON.parse(res._getData());
|
||||
expect(createdBooking.creationSource).toEqual(CreationSource.API_V1);
|
||||
expect(prismaMock.booking.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Recurring event-type", () => {
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, test, expect, vi } from "vitest";
|
||||
|
||||
import handler from "../../../pages/api/users/_post";
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
vi.mock("@calcom/lib/server/i18n", () => {
|
||||
return {
|
||||
getTranslation: (key: string) => {
|
||||
return () => key;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.stubEnv("CALCOM_LICENSE_KEY", undefined);
|
||||
|
||||
describe("POST /api/users", () => {
|
||||
test("should throw 401 if not system-wide admin", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "test@example.com",
|
||||
username: "test",
|
||||
},
|
||||
});
|
||||
req.isSystemWideAdmin = false;
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(401);
|
||||
});
|
||||
test("should throw a 400 if no email is provided", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
username: "test",
|
||||
},
|
||||
});
|
||||
req.isSystemWideAdmin = true;
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
test("should throw a 400 if no username is provided", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "test@example.com",
|
||||
},
|
||||
});
|
||||
req.isSystemWideAdmin = true;
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
test("should create user successfully", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "test@example.com",
|
||||
username: "test",
|
||||
},
|
||||
prisma: prismock,
|
||||
});
|
||||
req.isSystemWideAdmin = true;
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
const userQuery = await prismock.user.findFirst({
|
||||
where: {
|
||||
email: "test@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(userQuery).toEqual(
|
||||
expect.objectContaining({
|
||||
email: "test@example.com",
|
||||
username: "test",
|
||||
locked: false,
|
||||
organizationId: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should auto lock user if email is in watchlist", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "POST",
|
||||
body: {
|
||||
email: "test@example.com",
|
||||
username: "test",
|
||||
},
|
||||
prisma: prismock,
|
||||
});
|
||||
req.isSystemWideAdmin = true;
|
||||
|
||||
await prismock.watchlist.create({
|
||||
data: {
|
||||
type: "EMAIL",
|
||||
value: "test@example.com",
|
||||
severity: "CRITICAL",
|
||||
createdById: 1,
|
||||
},
|
||||
});
|
||||
|
||||
await handler(req, res);
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
const userQuery = await prismock.user.findFirst({
|
||||
where: {
|
||||
email: "test@example.com",
|
||||
},
|
||||
});
|
||||
|
||||
expect(userQuery).toEqual(
|
||||
expect.objectContaining({
|
||||
email: "test@example.com",
|
||||
username: "test",
|
||||
locked: true,
|
||||
organizationId: null,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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.86",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.116",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
@@ -60,6 +61,7 @@
|
||||
"helmet": "^7.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"jsforce": "^1.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.4",
|
||||
"nest-winston": "^1.9.4",
|
||||
"next-auth": "^4.22.1",
|
||||
@@ -89,6 +91,7 @@
|
||||
"@types/supertest": "^2.0.12",
|
||||
"jest": "^29.7.0",
|
||||
"jest-date-mock": "^1.0.10",
|
||||
"node-mocks-http": "^1.16.2",
|
||||
"prettier": "^2.8.6",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^6.3.3",
|
||||
|
||||
@@ -14,6 +14,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { RateLimitRepositoryFixture } from "test/fixtures/repository/rate-limit.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import { User, PlatformOAuthClient, Team, RateLimit } from "@calcom/prisma/client";
|
||||
@@ -24,7 +25,7 @@ describe("AppController", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let rateLimitRepositoryFixture: RateLimitRepositoryFixture;
|
||||
const userEmail = "app-rate-limits-e2e@api.com";
|
||||
const userEmail = `app-rate-limits-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let organization: Team;
|
||||
@@ -94,7 +95,9 @@ describe("AppController", () => {
|
||||
);
|
||||
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
organization = await organizationsRepositoryFixture.create({ name: "ecorp" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `app-rate-limits-organization-${randomString()}`,
|
||||
});
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
@@ -8,13 +8,13 @@ used in production, if you (and any entity that you represent) have agreed to,
|
||||
and are in compliance with, the Cal.com Subscription Terms available
|
||||
at https://cal.com/terms, or other agreements governing
|
||||
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
|
||||
and otherwise have a valid Cal.com Enterprise Edition subscription ("Commercial Subscription")
|
||||
and otherwise have a valid Cal.com Commercial License ("Commercial License")
|
||||
for the correct number of hosts as defined in the "Commercial Terms ("Hosts"). Subject to the foregoing sentence,
|
||||
you are free to modify this Software and publish patches to the Software. You agree
|
||||
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
|
||||
and to all such modifications and/or patches, and all such modifications and/or
|
||||
patches may only be used, copied, modified, displayed, distributed, or otherwise
|
||||
exploited with a valid Commercial Subscription for the correct number of hosts.
|
||||
exploited with a valid Commercial License for the correct number of hosts.
|
||||
Notwithstanding the foregoing, you may copy and modify the Software for development
|
||||
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
|
||||
its licensors (as applicable) retain all right, title and interest in and to all such
|
||||
|
||||
@@ -4,15 +4,15 @@
|
||||
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
|
||||
</a>
|
||||
|
||||
<a href="https://console.cal.com/">Get a License Key</a>
|
||||
<a href="https://go.cal.com/get-license">Get a License Key</a>
|
||||
</div>
|
||||
|
||||
# Enterprise Edition of API
|
||||
# Commercial License of API
|
||||
|
||||
Welcome to the Enterprise Edition ("/ee") of the Cal.com API.
|
||||
Welcome to the Commercial License of the Cal.com API.
|
||||
|
||||
Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All "Multiplayer APIs" are under a commercial license.
|
||||
|
||||
The [/ee](https://github.com/calcom/cal.com/tree/main/apps/api/v2/ee) subfolder is the place for all the **Enterprise Edition** features from our [hosted](https://cal.com/pricing) plan and enterprise-grade features for [Enterprise](https://cal.com/enterprise) such as SSO, SAML, OIDC, SCIM, SIEM and much more or [Platform](https://cal.com/platform) plan to build a marketplace.
|
||||
The [/ee](https://github.com/calcom/cal.com/tree/main/apps/api/v2/ee) subfolder is the place for all the **Commercial License** features from our [hosted](https://cal.com/pricing) plan.
|
||||
|
||||
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calcom/cal.com)). You are not allowed to use this code to host your own version of app.cal.com without obtaining a proper [license](https://console.cal.com/) first❗_
|
||||
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calcom/cal.com)). You are not allowed to use this code to host your own version of app.cal.com without obtaining a proper [license](https://go.cal.com/get-license) first❗_
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BookingsController_2024_04_15 } from "@/ee/bookings/2024-04-15/controllers/bookings.controller";
|
||||
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
@@ -11,7 +11,7 @@ import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, RedisModule, TokensModule, BillingModule],
|
||||
providers: [TokensRepository, OAuthFlowService, OAuthClientRepository, ApiKeyRepository],
|
||||
providers: [TokensRepository, OAuthFlowService, OAuthClientRepository, ApiKeysRepository],
|
||||
controllers: [BookingsController_2024_04_15],
|
||||
})
|
||||
export class BookingsModule_2024_04_15 {}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { GetBookingsOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/g
|
||||
import { MarkNoShowOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/mark-no-show.output";
|
||||
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
|
||||
import { VERSION_2024_04_15, VERSION_2024_06_11, VERSION_2024_06_14 } from "@/lib/api-versions";
|
||||
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
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";
|
||||
@@ -29,10 +29,12 @@ import {
|
||||
Query,
|
||||
NotFoundException,
|
||||
UseGuards,
|
||||
BadRequestException,
|
||||
} from "@nestjs/common";
|
||||
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";
|
||||
@@ -97,7 +99,7 @@ export class BookingsController_2024_04_15 {
|
||||
private readonly oAuthClientRepository: OAuthClientRepository,
|
||||
private readonly billingService: BillingService,
|
||||
private readonly config: ConfigService,
|
||||
private readonly apiKeyRepository: ApiKeyRepository
|
||||
private readonly apiKeyRepository: ApiKeysRepository
|
||||
) {}
|
||||
|
||||
@Get("/")
|
||||
@@ -188,18 +190,24 @@ export class BookingsController_2024_04_15 {
|
||||
throw new InternalServerErrorException("Could not create booking.");
|
||||
}
|
||||
|
||||
@Post("/:bookingId/cancel")
|
||||
@Post("/:bookingUid/cancel")
|
||||
async cancelBooking(
|
||||
@Req() req: BookingRequest,
|
||||
@Param("bookingId") bookingId: string,
|
||||
@Param("bookingUid") bookingUid: string,
|
||||
@Body() _: CancelBookingInput_2024_04_15,
|
||||
@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) {
|
||||
const isUidNumber = !isNaN(Number(bookingUid));
|
||||
|
||||
if (isUidNumber) {
|
||||
throw new BadRequestException("Please provide booking uid instead of booking id.");
|
||||
}
|
||||
|
||||
if (bookingUid) {
|
||||
try {
|
||||
req.body.id = parseInt(bookingId);
|
||||
req.body.uid = bookingUid;
|
||||
const res = await handleCancelBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
@@ -383,7 +391,11 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
});
|
||||
Object.assign(clone, { userId, ...oAuthParams, platformBookingLocation });
|
||||
clone.body = { ...clone.body, noEmail: !oAuthParams.arePlatformEmailsEnabled };
|
||||
clone.body = {
|
||||
...clone.body,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -411,6 +423,7 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
platformBookingLocation,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
});
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -126,4 +126,62 @@ 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;
|
||||
}
|
||||
>;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
teamMemberEmail?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
crmAppSlug?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiPropertyOptional()
|
||||
crmOwnerRecordType?: string;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bo
|
||||
import { InputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/input.service";
|
||||
import { OutputBookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/output.service";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { BookingSeatModule } from "@/modules/booking-seat/booking-seat.module";
|
||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||
@@ -29,8 +29,9 @@ import { Module } from "@nestjs/common";
|
||||
BookingsRepository_2024_08_13,
|
||||
EventTypesRepository_2024_06_14,
|
||||
BookingSeatRepository,
|
||||
ApiKeyRepository,
|
||||
ApiKeysRepository,
|
||||
],
|
||||
controllers: [BookingsController_2024_08_13],
|
||||
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
|
||||
})
|
||||
export class BookingsModule_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;
|
||||
|
||||
+141
-109
@@ -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: [
|
||||
{
|
||||
@@ -377,15 +309,114 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
placeholder: "matrix",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
{
|
||||
name: "video-url",
|
||||
type: "url",
|
||||
label: "video url",
|
||||
sources: [
|
||||
{
|
||||
id: "user",
|
||||
type: "user",
|
||||
label: "User",
|
||||
fieldRequired: true,
|
||||
},
|
||||
],
|
||||
editable: "user",
|
||||
required: false,
|
||||
placeholder: "add video url",
|
||||
disableOnPrefill: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -441,7 +472,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
console.log("asap data", JSON.stringify(data, null, 2));
|
||||
expect(data.attendees[0].name).toEqual(`${splitName.firstName} ${splitName.lastName}`);
|
||||
expect(data.attendees[0].bookingFieldsResponses.name).toEqual(splitName);
|
||||
} else {
|
||||
@@ -457,7 +487,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 +506,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",
|
||||
@@ -486,6 +516,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
bookingFieldsResponses: {
|
||||
"favorite-movie": "lord of the rings",
|
||||
"video-url": "http://video-url.com",
|
||||
},
|
||||
};
|
||||
return request(app.getHttpServer())
|
||||
@@ -502,6 +533,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.bookingFieldsResponses["favorite-movie"]).toEqual("lord of the rings");
|
||||
expect(data.bookingFieldsResponses["video-url"]).toEqual("http://video-url.com");
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
|
||||
+12
-5
@@ -16,6 +16,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -35,11 +36,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `confirm-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = "peer-coding";
|
||||
const eventTypeSlug = `confirm-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking1: Booking;
|
||||
let createdBooking2: Booking;
|
||||
@@ -64,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -77,13 +80,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-bookings-2024-08-13-${randomString()}-schedule`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: eventTypeSlug, length: 60 },
|
||||
{
|
||||
title: `confirm-bookings-2024-08-13-${randomString()}-event-type`,
|
||||
slug: `confirm-bookings-2024-08-13-${randomString()}-event-type-slug`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
+21
-8
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -92,7 +93,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = `admin-${Math.floor(Math.random() * 1000)}@example.com`;
|
||||
const authEmail = `confirm-emails-2024-08-13-admin-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -114,7 +115,9 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -139,7 +142,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -148,14 +151,19 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
@@ -171,7 +179,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -179,13 +187,18 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
|
||||
+23
-20
@@ -23,6 +23,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -103,7 +104,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = "admin@example.com";
|
||||
const authEmail = "team-emails-2024-08-13-user-admin@example.com";
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -128,7 +129,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({ name: `team-emails-organization-${randomString()}` });
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -152,7 +153,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `team-emails-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -163,7 +164,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "alice@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -172,7 +173,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "bob@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -181,7 +182,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -237,8 +238,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -278,8 +279,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -336,7 +337,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 2",
|
||||
name: `team-emails-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -347,7 +348,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "charlie@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -356,7 +357,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "dean@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -365,7 +366,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -421,8 +422,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -462,8 +463,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -965,17 +966,19 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await userRepositoryFixture.deleteByEmail(authEmail);
|
||||
await userRepositoryFixture.deleteByEmail(emailsEnabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member2.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsEnabledSetup.member1.id,
|
||||
emailsEnabledSetup.member1.email
|
||||
);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsDisabledSetup.member1.id,
|
||||
emailsDisabledSetup.member1.email
|
||||
emailsDisabledSetup.member2.email
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
+22
-11
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -101,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `user-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -116,7 +119,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -125,22 +128,26 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
@@ -160,7 +167,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -168,21 +175,25 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
|
||||
+12
-9
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -45,8 +46,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const teamUserEmail = "orgUser1team1@api.com";
|
||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
||||
const teamUserEmail = `reassign-bookings-2024-08-13-user1-${randomString()}@api.com`;
|
||||
const teamUserEmail2 = `reassign-bookings-2024-08-13-user2-${randomString()}@api.com`;
|
||||
let teamUser1: User;
|
||||
let teamUser2: User;
|
||||
|
||||
@@ -78,11 +79,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization team bookings" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `reassign-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `reassign-bookings-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -95,17 +98,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamUser1 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail,
|
||||
locale: "it",
|
||||
name: "orgUser1team1",
|
||||
name: `reassign-bookings-2024-08-13-user1-${randomString()}`,
|
||||
});
|
||||
|
||||
teamUser2 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail2,
|
||||
locale: "it",
|
||||
name: "orgUser2team1",
|
||||
name: `reassign-bookings-2024-08-13-user2-${randomString()}`,
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `reassign-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -164,8 +167,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
users: {
|
||||
connect: [{ id: teamUser1.id }, { id: teamUser2.id }],
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
|
||||
+26
-15
@@ -17,6 +17,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -41,11 +42,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 3;
|
||||
let recurringEventTypeId: number;
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
let createdRecurringBooking: RecurringBookingOutput_2024_08_13[];
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -67,7 +70,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -80,7 +85,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -89,8 +94,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -344,7 +349,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -380,7 +385,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -393,17 +400,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -551,7 +559,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -587,7 +595,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -600,17 +610,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
|
||||
+45
-44
@@ -19,6 +19,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -51,22 +52,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_seated@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper Seated";
|
||||
const emailAttendeeTwo = "mr_proper_friend_seated@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Friend Seated";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -88,7 +90,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -101,15 +105,15 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
@@ -123,8 +127,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
@@ -200,7 +204,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
@@ -272,7 +276,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(2);
|
||||
// note(Lauris): first attendee is from previous test request
|
||||
@@ -366,7 +370,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -401,7 +405,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -575,7 +579,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
const attendee = createdSeatedBooking.attendees.find((a) => a.seatUid === body.seatUid);
|
||||
@@ -629,7 +633,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
@@ -681,22 +685,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_first@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper First";
|
||||
const emailAttendeeTwo = "mr_proper_second@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Second";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -718,7 +723,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -731,20 +738,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
seatsPerTimeSlot: 3,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -753,14 +757,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 3,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -836,7 +837,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -871,7 +872,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -949,7 +950,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(2);
|
||||
const firstAttendee = firstBooking.attendees.find(
|
||||
@@ -1003,7 +1004,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(2);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
|
||||
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -52,8 +53,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 = `team-bookings-user1-${randomString()}@api.com`;
|
||||
const teamUserEmail2 = `team-bookings-user2-${randomString()}@api.com`;
|
||||
let teamUser: User;
|
||||
let teamUser2: User;
|
||||
|
||||
@@ -61,6 +62,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
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(
|
||||
teamUserEmail,
|
||||
@@ -85,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: {
|
||||
@@ -100,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: {
|
||||
@@ -133,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,
|
||||
};
|
||||
@@ -196,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: [],
|
||||
@@ -211,8 +218,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
team: {
|
||||
connect: { id: team1.id },
|
||||
},
|
||||
title: "Phone Only Event Type",
|
||||
slug: "phone-only-event-type",
|
||||
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: phoneOnlyEventTypeSlug,
|
||||
length: 15,
|
||||
assignAllTeamMembers: false,
|
||||
hosts: {
|
||||
@@ -288,8 +295,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
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: [],
|
||||
@@ -554,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}`)
|
||||
|
||||
@@ -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 },
|
||||
},
|
||||
@@ -158,6 +167,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
timeZone: "Europe/Rome",
|
||||
},
|
||||
},
|
||||
rating: 10,
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
@@ -354,6 +364,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());
|
||||
@@ -386,6 +397,12 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(createdAtDate.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||
expect(createdAtDate.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
|
||||
|
||||
// Check updatedAt date is between the time of the request and after the request
|
||||
expect(data.updatedAt).toBeDefined();
|
||||
const updatedAtDate = data.updatedAt ? new Date(data.updatedAt) : null;
|
||||
expect(updatedAtDate?.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||
expect(updatedAtDate?.getTime()).toBeLessThanOrEqual(afterCreate.getTime());
|
||||
|
||||
expect(data.metadata).toEqual(body.metadata);
|
||||
createdBooking = data;
|
||||
} else {
|
||||
@@ -518,6 +535,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);
|
||||
@@ -535,6 +553,30 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get a booking with rating", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${bookingInThePast.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
const responseBody: GetBookingOutput_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).toEqual(bookingInThePast.id);
|
||||
expect(data.uid).toEqual(bookingInThePast.uid);
|
||||
expect(data.rating).toEqual(bookingInThePast.rating);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get 1 recurrence of a recurring booking", async () => {
|
||||
const recurrenceUid = createdRecurringBooking[0].uid;
|
||||
return request(app.getHttpServer())
|
||||
@@ -552,6 +594,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);
|
||||
@@ -856,7 +900,63 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
it("should should get bookings by before specified end time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?afterStart=${createdRecurringBooking[0].start}`)
|
||||
.get(`/v2/bookings?beforeEnd=${createdRecurringBooking[0].end}`)
|
||||
.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(3);
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings after specified update time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[1].updatedAt}`)
|
||||
.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(2);
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings before specified update time", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?beforeUpdatedAt=${createdRecurringBooking[0].updatedAt}`)
|
||||
.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(3);
|
||||
});
|
||||
});
|
||||
|
||||
it("should should get bookings in update range", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/bookings?afterUpdatedAt=${createdRecurringBooking[0].updatedAt}&beforeUpdatedAt=${createdRecurringBooking[2].updatedAt}`
|
||||
)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
@@ -991,6 +1091,46 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data[1].start).toEqual(createdBooking.start);
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by updated in descending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=desc`)
|
||||
.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(2);
|
||||
expect(data[0].updatedAt).toEqual(createdBooking.updatedAt);
|
||||
expect(data[1].updatedAt).toEqual(bookingInThePast.updatedAt?.toISOString());
|
||||
});
|
||||
});
|
||||
|
||||
it("should should sort bookings by updated in ascending order", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings?eventTypeId=${eventTypeId}&sortUpdatedAt=asc`)
|
||||
.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(2);
|
||||
expect(data[0].updatedAt).toEqual(bookingInThePast.updatedAt?.toISOString());
|
||||
expect(data[1].updatedAt).toEqual(createdBooking.updatedAt);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("reschedule bookings", () => {
|
||||
@@ -1021,6 +1161,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);
|
||||
@@ -1078,6 +1220,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());
|
||||
@@ -1158,6 +1302,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);
|
||||
@@ -1190,6 +1336,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);
|
||||
@@ -1228,6 +1376,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);
|
||||
@@ -1285,6 +1435,108 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("who cancelled and rescheduled a booking", () => {
|
||||
it("should return who cancelled a booking", async () => {
|
||||
const cancelledByEmail = `user-bookings-canceller-${randomString()}@canceller.com`;
|
||||
const cancelledBooking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${eventTypeId}`,
|
||||
title: "booking title",
|
||||
startTime: "2050-09-05T11:00:00.000Z",
|
||||
endTime: "2050-09-05T12:00:00.000Z",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
},
|
||||
status: "CANCELLED",
|
||||
cancelledBy: cancelledByEmail,
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
guests: [],
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${cancelledBooking.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
await bookingsRepositoryFixture.deleteById(cancelledBooking.id);
|
||||
const responseBody: GetBookingOutput_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.uid).toEqual(cancelledBooking.uid);
|
||||
expect(data.cancelledByEmail).toEqual(cancelledByEmail);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("should return who rescheduled a booking", async () => {
|
||||
const rescheduledByEmail = `user-bookings-rescheduler-${randomString()}@rescheduler.com`;
|
||||
const rescheduledBooking = await bookingsRepositoryFixture.create({
|
||||
uid: `booking-uid-${eventTypeId}`,
|
||||
title: "booking title",
|
||||
startTime: "2050-09-05T11:00:00.000Z",
|
||||
endTime: "2050-09-05T12:00:00.000Z",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
},
|
||||
status: "CANCELLED",
|
||||
rescheduledBy: rescheduledByEmail,
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: "tester",
|
||||
email: "tester@example.com",
|
||||
guests: [],
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${rescheduledBooking.uid}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200)
|
||||
.then(async (response) => {
|
||||
await bookingsRepositoryFixture.deleteById(rescheduledBooking.id);
|
||||
const responseBody: GetBookingOutput_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.uid).toEqual(rescheduledBooking.uid);
|
||||
expect(data.rescheduledByEmail).toEqual(rescheduledByEmail);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function responseDataIsRecurranceBooking(data: any): data is RecurringBookingOutput_2024_08_13 {
|
||||
return (
|
||||
!Array.isArray(data) &&
|
||||
|
||||
+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
|
||||
);
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@ import {
|
||||
} from "@/ee/bookings/2024-08-13/services/output.service";
|
||||
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
|
||||
import { hashAPIKey, isApiKey, stripApiKey } from "@/lib/api-key";
|
||||
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { BookingSeatRepository } from "@/modules/booking-seat/booking-seat.repository";
|
||||
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
|
||||
import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.service";
|
||||
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";
|
||||
@@ -73,7 +74,7 @@ export class InputBookingsService_2024_08_13 {
|
||||
private readonly eventTypesRepository: EventTypesRepository_2024_06_14,
|
||||
private readonly bookingsRepository: BookingsRepository_2024_08_13,
|
||||
private readonly config: ConfigService,
|
||||
private readonly apiKeyRepository: ApiKeyRepository,
|
||||
private readonly apiKeyRepository: ApiKeysRepository,
|
||||
private readonly bookingSeatRepository: BookingSeatRepository
|
||||
) {}
|
||||
|
||||
@@ -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;
|
||||
@@ -220,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;
|
||||
@@ -317,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;
|
||||
@@ -453,11 +463,18 @@ export class InputBookingsService_2024_08_13 {
|
||||
teamIds: queryParams.teamsIds || (queryParams.teamId ? [queryParams.teamId] : undefined),
|
||||
eventTypeIds:
|
||||
queryParams.eventTypeIds || (queryParams.eventTypeId ? [queryParams.eventTypeId] : undefined),
|
||||
afterUpdatedDate: queryParams.afterUpdatedAt,
|
||||
beforeUpdatedDate: queryParams.beforeUpdatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
transformGetBookingsSort(queryParams: GetBookingsInput_2024_08_13) {
|
||||
if (!queryParams.sortStart && !queryParams.sortEnd && !queryParams.sortCreated) {
|
||||
if (
|
||||
!queryParams.sortStart &&
|
||||
!queryParams.sortEnd &&
|
||||
!queryParams.sortCreated &&
|
||||
!queryParams.sortUpdatedAt
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -465,6 +482,7 @@ export class InputBookingsService_2024_08_13 {
|
||||
sortStart: queryParams.sortStart,
|
||||
sortEnd: queryParams.sortEnd,
|
||||
sortCreated: queryParams.sortCreated,
|
||||
sortUpdated: queryParams.sortUpdatedAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,9 @@ export class OutputBookingsService_2024_08_13 {
|
||||
hosts: [this.getHost(databaseBooking.user)],
|
||||
status: databaseBooking.status.toLowerCase(),
|
||||
cancellationReason: databaseBooking.cancellationReason || undefined,
|
||||
cancelledByEmail: databaseBooking.cancelledBy || undefined,
|
||||
reschedulingReason: bookingResponses?.rescheduledReason,
|
||||
rescheduledByEmail: databaseBooking.rescheduledBy || undefined,
|
||||
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
||||
start: databaseBooking.startTime,
|
||||
end: databaseBooking.endTime,
|
||||
@@ -130,6 +132,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
meetingUrl: location,
|
||||
absentHost: !!databaseBooking.noShowHost,
|
||||
createdAt: databaseBooking.createdAt,
|
||||
updatedAt: databaseBooking.updatedAt,
|
||||
rating: databaseBooking.rating,
|
||||
};
|
||||
|
||||
const bookingTransformed = plainToClass(BookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
||||
@@ -198,7 +202,9 @@ export class OutputBookingsService_2024_08_13 {
|
||||
hosts: [this.getHost(databaseBooking.user)],
|
||||
status: databaseBooking.status.toLowerCase(),
|
||||
cancellationReason: databaseBooking.cancellationReason || undefined,
|
||||
cancelledByEmail: databaseBooking.cancelledBy || undefined,
|
||||
reschedulingReason: bookingResponses?.rescheduledReason,
|
||||
rescheduledByEmail: databaseBooking.rescheduledBy || undefined,
|
||||
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
||||
start: databaseBooking.startTime,
|
||||
end: databaseBooking.endTime,
|
||||
@@ -221,6 +227,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
absentHost: !!databaseBooking.noShowHost,
|
||||
bookingFieldsResponses: databaseBooking.responses,
|
||||
createdAt: databaseBooking.createdAt,
|
||||
updatedAt: databaseBooking.updatedAt,
|
||||
rating: databaseBooking.rating,
|
||||
};
|
||||
|
||||
const bookingTransformed = plainToClass(RecurringBookingOutput_2024_08_13, booking, {
|
||||
@@ -267,6 +275,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
meetingUrl: location,
|
||||
absentHost: !!databaseBooking.noShowHost,
|
||||
createdAt: databaseBooking.createdAt,
|
||||
updatedAt: databaseBooking.updatedAt,
|
||||
rating: databaseBooking.rating,
|
||||
};
|
||||
|
||||
const parsed = plainToClass(GetSeatedBookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
||||
@@ -276,7 +286,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
|
||||
const attendeeData = {
|
||||
@@ -293,7 +304,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
@@ -372,6 +384,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
recurringBookingUid: databaseBooking.recurringEventId,
|
||||
absentHost: !!databaseBooking.noShowHost,
|
||||
createdAt: databaseBooking.createdAt,
|
||||
updatedAt: databaseBooking.updatedAt,
|
||||
rating: databaseBooking.rating,
|
||||
};
|
||||
|
||||
const parsed = plainToClass(GetRecurringSeatedBookingOutput_2024_08_13, booking, {
|
||||
@@ -383,7 +397,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const { responses } = safeParse(
|
||||
seatedBookingDataSchema,
|
||||
attendee.bookingSeat?.data,
|
||||
defaultSeatedBookingData
|
||||
defaultSeatedBookingData,
|
||||
false
|
||||
);
|
||||
|
||||
const attendeeData = {
|
||||
@@ -400,7 +415,8 @@ export class OutputBookingsService_2024_08_13 {
|
||||
attendeeParsed.metadata = safeParse(
|
||||
seatedBookingMetadataSchema,
|
||||
attendee.bookingSeat?.metadata,
|
||||
defaultSeatedBookingMetadata
|
||||
defaultSeatedBookingMetadata,
|
||||
false
|
||||
);
|
||||
// note(Lauris): as of now email is not returned for privacy
|
||||
delete attendeeParsed.bookingFieldsResponses.email;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
Post,
|
||||
Body,
|
||||
} from "@nestjs/common";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { ApiOperation, ApiParam, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { User } from "@prisma/client";
|
||||
import { plainToClass } from "class-transformer";
|
||||
import { Request } from "express";
|
||||
@@ -123,10 +123,15 @@ export class CalendarsController {
|
||||
};
|
||||
}
|
||||
|
||||
@ApiParam({
|
||||
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
|
||||
type: String,
|
||||
name: "calendar",
|
||||
})
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@Get("/:calendar/connect")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: "Get connect URL" })
|
||||
@ApiOperation({ summary: "Get oAuth connect URL" })
|
||||
async redirect(
|
||||
@Req() req: Request,
|
||||
@Headers("Authorization") authorization: string,
|
||||
@@ -146,10 +151,15 @@ export class CalendarsController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiParam({
|
||||
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
|
||||
type: String,
|
||||
name: "calendar",
|
||||
})
|
||||
@Get("/:calendar/save")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Redirect(undefined, 301)
|
||||
@ApiOperation({ summary: "Save a calendar" })
|
||||
@ApiOperation({ summary: "Save an oAuth calendar credentials" })
|
||||
async save(
|
||||
@Query("state") state: string,
|
||||
@Query("code") code: string,
|
||||
@@ -177,6 +187,11 @@ export class CalendarsController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiParam({
|
||||
enum: [APPLE_CALENDAR],
|
||||
type: String,
|
||||
name: "calendar",
|
||||
})
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@Post("/:calendar/credentials")
|
||||
@ApiOperation({ summary: "Sync credentials" })
|
||||
@@ -198,6 +213,11 @@ export class CalendarsController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiParam({
|
||||
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
|
||||
type: String,
|
||||
name: "calendar",
|
||||
})
|
||||
@Get("/:calendar/check")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(ApiAuthGuard, PermissionsGuard)
|
||||
@@ -219,6 +239,11 @@ export class CalendarsController {
|
||||
}
|
||||
}
|
||||
|
||||
@ApiParam({
|
||||
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
|
||||
type: String,
|
||||
name: "calendar",
|
||||
})
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@Post("/:calendar/disconnect")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
+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 })
|
||||
|
||||
+56
-17
@@ -19,6 +19,7 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
@@ -86,9 +87,9 @@ describe("Event types Endpoints", () => {
|
||||
let schedulesRepostoryFixture: SchedulesRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
const userEmail = "event-types-test-e2e@api.com";
|
||||
const falseTestEmail = "false-event-types@api.com";
|
||||
const name = "bob-the-builder";
|
||||
const userEmail = `event-types-2024-06-14-user-${randomString()}@api.com`;
|
||||
const falseTestEmail = `event-types-2024-06-14-false-user-${randomString()}@api.com`;
|
||||
const name = `event-types-2024-06-14-user-${randomString()}`;
|
||||
const username = name;
|
||||
let eventType: EventTypeOutput_2024_06_14;
|
||||
let user: User;
|
||||
@@ -186,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({
|
||||
@@ -197,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({
|
||||
@@ -218,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
|
||||
);
|
||||
|
||||
@@ -241,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",
|
||||
});
|
||||
|
||||
@@ -361,6 +377,15 @@ describe("Event types Endpoints", () => {
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
},
|
||||
{
|
||||
type: "url",
|
||||
label: "Video Url",
|
||||
slug: "video-url",
|
||||
required: true,
|
||||
placeholder: "add video url",
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
},
|
||||
],
|
||||
scheduleId: firstSchedule.id,
|
||||
bookingLimitsCount: {
|
||||
@@ -462,6 +487,16 @@ describe("Event types Endpoints", () => {
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
type: "url",
|
||||
label: "Video Url",
|
||||
slug: "video-url",
|
||||
required: true,
|
||||
placeholder: "add video url",
|
||||
disableOnPrefill: true,
|
||||
hidden: false,
|
||||
isDefault: false,
|
||||
},
|
||||
{ ...defaultResponseBookingFieldTitle },
|
||||
{ ...defaultResponseBookingFieldNotes },
|
||||
{ ...defaultResponseBookingFieldGuests },
|
||||
@@ -1221,7 +1256,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,
|
||||
@@ -1708,7 +1745,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,
|
||||
|
||||
@@ -7,11 +7,7 @@ import { InputEventTransformed_2024_06_14 } from "@calcom/platform-types";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository_2024_06_14 {
|
||||
constructor(
|
||||
private readonly dbRead: PrismaReadService,
|
||||
private readonly dbWrite: PrismaWriteService,
|
||||
private usersService: UsersService
|
||||
) {}
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async createUserEventType(
|
||||
userId: number,
|
||||
@@ -47,6 +43,13 @@ export class EventTypesRepository_2024_06_14 {
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeWithHosts(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: { hosts: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getUserEventType(userId: number, eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findFirst({
|
||||
where: {
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsEnum, IsString, ValidateNested } from "class-validator";
|
||||
import { IsEnum, IsString, ValidateNested, IsOptional } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
@@ -10,8 +10,9 @@ class Data {
|
||||
callId!: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty()
|
||||
agentId!: string;
|
||||
agentId?: string;
|
||||
}
|
||||
|
||||
export class CreatePhoneCallOutput {
|
||||
|
||||
@@ -26,7 +26,8 @@ import {
|
||||
Headers,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { ApiExcludeController } from "@nestjs/swagger";
|
||||
import { ApiOperation } from "@nestjs/swagger";
|
||||
import { Request } from "express";
|
||||
|
||||
import { APPS_READ, GOOGLE_CALENDAR_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -41,7 +42,7 @@ const CALENDAR_SCOPES = [
|
||||
path: "/v2/gcal",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@DocsTags("Platform / Google Calendar")
|
||||
@ApiExcludeController(true)
|
||||
export class GcalController {
|
||||
private readonly logger = new Logger("Platform Gcal Provider");
|
||||
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -8,10 +8,11 @@ import { MeModule } from "@/ee/me/me.module";
|
||||
import { ProviderModule } from "@/ee/provider/provider.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||
import { SlotsModule } from "@/modules/slots/slots.module";
|
||||
import { SlotsModule_2024_04_15 } from "@/modules/slots/slots-2024-04-15/slots.module";
|
||||
import { SlotsModule_2024_09_04 } from "@/modules/slots/slots-2024-09-04/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";
|
||||
|
||||
@@ -29,7 +30,8 @@ import { Module } from "@nestjs/common";
|
||||
BookingsModule_2024_04_15,
|
||||
BookingsModule_2024_08_13,
|
||||
TeamsMembershipsModule,
|
||||
SlotsModule,
|
||||
SlotsModule_2024_04_15,
|
||||
SlotsModule_2024_09_04,
|
||||
TeamsModule,
|
||||
],
|
||||
})
|
||||
|
||||
+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,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
VERSION_2024_06_11,
|
||||
VERSION_2024_06_14,
|
||||
VERSION_2024_08_13,
|
||||
VERSION_2024_09_04,
|
||||
} from "@calcom/platform-constants";
|
||||
|
||||
export const API_VERSIONS_VALUES: VersionValue = API_VERSIONS as unknown as VersionValue;
|
||||
@@ -13,8 +14,10 @@ export const VERSION_2024_06_14_VALUE: VersionValue = VERSION_2024_06_14 as unkn
|
||||
export const VERSION_2024_06_11_VALUE: VersionValue = VERSION_2024_06_11 as unknown as VersionValue;
|
||||
export const VERSION_2024_04_15_VALUE: VersionValue = VERSION_2024_04_15 as unknown as VersionValue;
|
||||
export const VERSION_2024_08_13_VALUE: VersionValue = VERSION_2024_08_13 as unknown as VersionValue;
|
||||
export const VERSION_2024_09_04_VALUE: VersionValue = VERSION_2024_09_04 as unknown as VersionValue;
|
||||
|
||||
export { VERSION_2024_04_15 };
|
||||
export { VERSION_2024_06_11 };
|
||||
export { VERSION_2024_06_14 };
|
||||
export { VERSION_2024_08_13 };
|
||||
export { VERSION_2024_09_04 };
|
||||
|
||||
@@ -3,20 +3,22 @@ import { ZodSchema } from "zod";
|
||||
|
||||
const logger = new Logger("safeParse");
|
||||
|
||||
export function safeParse<T>(schema: ZodSchema<T>, value: unknown, defaultValue: T): T {
|
||||
export function safeParse<T>(schema: ZodSchema<T>, value: unknown, defaultValue: T, logError = true): T {
|
||||
const result = schema.safeParse(value);
|
||||
if (result.success) {
|
||||
return result.data;
|
||||
} else {
|
||||
const errorStack = new Error().stack;
|
||||
|
||||
logger.error(
|
||||
`Zod parsing failed.\n` +
|
||||
`1. Schema: ${schema.description || "UnnamedSchema"}\n` +
|
||||
`2. Input: ${JSON.stringify(value, null, 2)}\n` +
|
||||
`3. Zod Error: ${result.error}\n` +
|
||||
`4. Call Stack: ${errorStack}`
|
||||
);
|
||||
if (logError) {
|
||||
logger.error(
|
||||
`Zod parsing failed.\n` +
|
||||
`1. Schema: ${schema.description || "UnnamedSchema"}\n` +
|
||||
`2. Input: ${JSON.stringify(value, null, 2)}\n` +
|
||||
`3. Zod Error: ${result.error}\n` +
|
||||
`4. Call Stack: ${errorStack}`
|
||||
);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { ApiKeyRepository } from "@/modules/api-key/api-key-repository";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [ApiKeyRepository],
|
||||
exports: [ApiKeyRepository],
|
||||
})
|
||||
export class ApiKeyModule {}
|
||||
+17
-1
@@ -3,7 +3,7 @@ import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeyRepository {
|
||||
export class ApiKeysRepository {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
|
||||
async getApiKeyFromHash(hashedKey: string) {
|
||||
@@ -13,4 +13,20 @@ export class ApiKeyRepository {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async getTeamApiKeys(teamId: number) {
|
||||
return this.dbRead.prisma.apiKey.findMany({
|
||||
where: {
|
||||
teamId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async deleteById(id: string) {
|
||||
return this.dbWrite.prisma.apiKey.delete({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { ApiKeysController } from "@/modules/api-keys/controllers/api-keys.controller";
|
||||
import { ApiKeysService } from "@/modules/api-keys/services/api-keys.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [ApiKeysRepository, ApiKeysService],
|
||||
controllers: [ApiKeysController],
|
||||
exports: [ApiKeysRepository, ApiKeysService],
|
||||
})
|
||||
export class ApiKeysModule {}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { RefreshApiKeyInput } from "@/modules/api-keys/inputs/refresh-api-key.input";
|
||||
import { RefreshApiKeyOutput } from "@/modules/api-keys/outputs/refresh-api-key.output";
|
||||
import { ApiKeysService } from "@/modules/api-keys/services/api-keys.service";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { ApiAuthGuardRequest } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { Body, Controller, HttpCode, HttpStatus, Post, Req, UseGuards } from "@nestjs/common";
|
||||
import { ApiTags, ApiHeader, ApiOperation } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/api-keys",
|
||||
version: API_VERSIONS_VALUES,
|
||||
})
|
||||
@UseGuards(ApiAuthGuard)
|
||||
@ApiTags("Api Keys")
|
||||
export class ApiKeysController {
|
||||
constructor(private readonly apiKeysService: ApiKeysService) {}
|
||||
|
||||
@ApiOperation({
|
||||
summary: "Refresh API Key",
|
||||
description: `Generate a new API key and delete the current one. Provide API key to refresh as a Bearer token in the Authorization header (e.g. "Authorization: Bearer <apiKey>").`,
|
||||
})
|
||||
@ApiHeader({
|
||||
name: "Authorization",
|
||||
description: "Bearer <apiKey>",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
})
|
||||
@Post("/refresh")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async refresh(
|
||||
@Body() body: RefreshApiKeyInput,
|
||||
@GetUser("id") userId: number,
|
||||
@Req() request: ApiAuthGuardRequest
|
||||
): Promise<RefreshApiKeyOutput> {
|
||||
const currentApiKey = await this.apiKeysService.getRequestApiKey(request);
|
||||
const newApiKey = await this.apiKeysService.refreshApiKey(userId, currentApiKey, body);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: {
|
||||
apiKey: newApiKey,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { RefreshApiKeyInput } from "@/modules/api-keys/inputs/refresh-api-key.input";
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsInt, IsOptional, IsString } from "class-validator";
|
||||
|
||||
export class CreateApiKeyInput extends RefreshApiKeyInput {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@ApiPropertyOptional({
|
||||
description: "For which team or organization is the api key.",
|
||||
})
|
||||
readonly teamId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@ApiPropertyOptional({
|
||||
description: "Store additional note about this api key.",
|
||||
example: "This is an api key for development purposes.",
|
||||
})
|
||||
readonly note?: string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { IsBoolean, IsInt, IsOptional, Min } from "class-validator";
|
||||
|
||||
export class RefreshApiKeyInput {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@ApiPropertyOptional({
|
||||
description: "For how many days is managed organization api key valid. Defaults to 30 days.",
|
||||
example: 60,
|
||||
default: 30,
|
||||
minimum: 1,
|
||||
})
|
||||
readonly apiKeyDaysValid?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@ApiPropertyOptional({ description: "If true, organization api key never expires.", example: true })
|
||||
readonly apiKeyNeverExpires?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsString } from "class-validator";
|
||||
|
||||
export class ApiKeyOutput {
|
||||
@IsString()
|
||||
@Expose()
|
||||
@DocsProperty()
|
||||
readonly apiKey!: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { ApiKeyOutput } from "@/modules/api-keys/outputs/api-key.output";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import { IsEnum, ValidateNested } from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class RefreshApiKeyOutput {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@ApiProperty({ type: ApiKeyOutput })
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => ApiKeyOutput)
|
||||
data!: ApiKeyOutput;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { hashAPIKey, stripApiKey } from "@/lib/api-key";
|
||||
import { AuthMethods } from "@/lib/enums/auth-methods";
|
||||
import { ApiKeysRepository } from "@/modules/api-keys/api-keys-repository";
|
||||
import { CreateApiKeyInput } from "@/modules/api-keys/inputs/create-api-key.input";
|
||||
import { RefreshApiKeyInput } from "@/modules/api-keys/inputs/refresh-api-key.input";
|
||||
import { ApiAuthGuardRequest } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
import { createApiKeyHandler } from "@calcom/platform-libraries";
|
||||
|
||||
@Injectable()
|
||||
export class ApiKeysService {
|
||||
constructor(
|
||||
private readonly apiKeysRepository: ApiKeysRepository,
|
||||
private readonly config: ConfigService
|
||||
) {}
|
||||
|
||||
async getRequestApiKey(request: ApiAuthGuardRequest) {
|
||||
if (request.authMethod !== AuthMethods.API_KEY) {
|
||||
throw new UnauthorizedException(
|
||||
"ApiKeysService - This endpoint can only be accessed using an API key by providing 'Authorization: Bearer <apiKey>' header"
|
||||
);
|
||||
}
|
||||
const apiKey = request.get("Authorization")?.replace("Bearer ", "");
|
||||
if (!apiKey) {
|
||||
throw new UnauthorizedException("ApiKeysService - No API key provided");
|
||||
}
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
async createApiKey(authUserId: number, createApiKeyInput: CreateApiKeyInput) {
|
||||
if (createApiKeyInput.apiKeyDaysValid && createApiKeyInput.apiKeyNeverExpires) {
|
||||
throw new BadRequestException(
|
||||
"ApiKeysService -Cannot set both apiKeyDaysValid and apiKeyNeverExpires. It has to be either or none of them."
|
||||
);
|
||||
}
|
||||
|
||||
const defaultApiKeyDaysValid = 30;
|
||||
const apiKeyExpiresAfterDays = createApiKeyInput.apiKeyDaysValid
|
||||
? createApiKeyInput.apiKeyDaysValid
|
||||
: defaultApiKeyDaysValid;
|
||||
const apiKeyExpiresAt = DateTime.utc().plus({ days: apiKeyExpiresAfterDays }).toJSDate();
|
||||
const apiKey = await createApiKeyHandler({
|
||||
ctx: {
|
||||
user: {
|
||||
id: authUserId,
|
||||
},
|
||||
},
|
||||
input: {
|
||||
note: createApiKeyInput.note,
|
||||
neverExpires: !!createApiKeyInput.apiKeyNeverExpires,
|
||||
expiresAt: apiKeyExpiresAt,
|
||||
teamId: createApiKeyInput.teamId,
|
||||
},
|
||||
});
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
async refreshApiKey(authUserId: number, apiKey: string, refreshApiKeyInput: RefreshApiKeyInput) {
|
||||
const strippedApiKey = stripApiKey(apiKey, this.config.get<string>("api.keyPrefix"));
|
||||
const apiKeyHash = hashAPIKey(strippedApiKey);
|
||||
const apiKeyInDb = await this.apiKeysRepository.getApiKeyFromHash(apiKeyHash);
|
||||
if (!apiKeyInDb) {
|
||||
throw new UnauthorizedException("ApiKeysService - provided api key is not valid.");
|
||||
}
|
||||
|
||||
const newApiKey = await this.createApiKey(authUserId, {
|
||||
...refreshApiKeyInput,
|
||||
note: apiKeyInDb.note || undefined,
|
||||
teamId: apiKeyInDb.teamId || undefined,
|
||||
});
|
||||
|
||||
await this.apiKeysRepository.deleteById(apiKeyInDb.id);
|
||||
|
||||
return newApiKey;
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_
|
||||
import { AtomsRepository } from "@/modules/atoms/atoms.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { TeamsEventTypesService } from "@/modules/teams/event-types/services/teams-event-types.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, NotFoundException, ForbiddenException } from "@nestjs/common";
|
||||
@@ -122,9 +122,10 @@ export class EventTypesAtomService {
|
||||
async updateEventType(eventTypeId: number, body: TUpdateEventTypeInputSchema, user: UserWithProfile) {
|
||||
await this.eventTypeService.checkCanUpdateEventType(user.id, eventTypeId, body.scheduleId);
|
||||
const eventTypeUser = await this.eventTypeService.getUserToUpdateEvent(user);
|
||||
const bookingFields = [...(body.bookingFields || [])];
|
||||
const bookingFields = body.bookingFields ? [...body.bookingFields] : undefined;
|
||||
|
||||
if (
|
||||
bookingFields?.length &&
|
||||
!bookingFields.find((field) => field.type === "email") &&
|
||||
!bookingFields.find((field) => field.type === "phone")
|
||||
) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiKeyModule } from "@/modules/api-key/api-key.module";
|
||||
import { ApiKeysModule } from "@/modules/api-keys/api-keys.module";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
|
||||
import { ApiAuthStrategy } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
@@ -17,7 +17,7 @@ import { PassportModule } from "@nestjs/passport";
|
||||
imports: [
|
||||
PassportModule,
|
||||
RedisModule,
|
||||
ApiKeyModule,
|
||||
ApiKeysModule,
|
||||
UsersModule,
|
||||
MembershipsModule,
|
||||
TokensModule,
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { ExecutionContext } from "@nestjs/common";
|
||||
import { createParamDecorator } from "@nestjs/common";
|
||||
|
||||
export const GetOptionalUser = createParamDecorator<
|
||||
keyof ApiAuthGuardUser | (keyof ApiAuthGuardUser)[],
|
||||
ExecutionContext
|
||||
>((data, ctx) => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const user = request.user as ApiAuthGuardUser;
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.reduce((prev, curr) => {
|
||||
return {
|
||||
...prev,
|
||||
[curr]: user[curr],
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
if (data) {
|
||||
return user[data];
|
||||
}
|
||||
|
||||
return user;
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createParamDecorator, ExecutionContext, UnauthorizedException } from "@nestjs/common";
|
||||
|
||||
export const GetOrgId = createParamDecorator((_: unknown, ctx: ExecutionContext): number => {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
const organizationId = request.organizationId;
|
||||
|
||||
if (!organizationId) {
|
||||
throw new UnauthorizedException("GetOrgId decorator: Organization ID not found");
|
||||
}
|
||||
|
||||
return organizationId;
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PlatformPlanGuard } from "@/modules/auth/guards/billing/platform-plan.guard";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { createMock } from "@golevelup/ts-jest";
|
||||
import { ExecutionContext } from "@nestjs/common";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { ApiAuthGuardUser } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { PlatformPlanType } from "@/modules/billing/types";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationsMembershipRepository } from "@/modules/organizations/repositories/organizations-membership.repository";
|
||||
import { OrganizationsMembershipRepository } from "@/modules/organizations/memberships/organizations-membership.repository";
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { NO_AUTH_PROVIDED_MESSAGE } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
|
||||
export class OptionalApiAuthGuard extends ApiAuthGuard {
|
||||
handleRequest(error: Error, user: any) {
|
||||
// note(Lauris): optional means that auth is not required but if it is invalid then still throw error.
|
||||
const noAuthProvided = error && error.message === NO_AUTH_PROVIDED_MESSAGE;
|
||||
if (user || noAuthProvided || !error) {
|
||||
return user || null;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MembershipRoles } from "@/modules/auth/decorators/roles/membership-roles.decorator";
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { OrganizationsService } from "@/modules/organizations/services/organizations.service";
|
||||
import { OrganizationsService } from "@/modules/organizations/index/organizations.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
@@ -20,10 +20,10 @@ export class OrganizationRolesGuard implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user: UserWithProfile = request.user;
|
||||
const organizationId = user ? this.usersService.getUserMainOrgId(user) : null;
|
||||
const organizationId = this.getOrganizationId(context);
|
||||
|
||||
if (!user || !organizationId) {
|
||||
throw new ForbiddenException("No organization associated with the user.");
|
||||
throw new ForbiddenException("OrganizationRolesGuard - No organization associated with the user.");
|
||||
}
|
||||
|
||||
await this.isPlatform(organizationId);
|
||||
@@ -40,13 +40,25 @@ export class OrganizationRolesGuard implements CanActivate {
|
||||
async isPlatform(organizationId: number) {
|
||||
const isPlatform = await this.organizationsService.isPlatform(organizationId);
|
||||
if (!isPlatform) {
|
||||
throw new ForbiddenException("Organization is not a platform (SHP).");
|
||||
throw new ForbiddenException("OrganizationRolesGuard - Organization is not a platform (SHP).");
|
||||
}
|
||||
}
|
||||
|
||||
getOrganizationId(context: ExecutionContext) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const user: UserWithProfile = request.user;
|
||||
const authMethodOrganizationId = request.organizationId;
|
||||
if (authMethodOrganizationId) return authMethodOrganizationId;
|
||||
|
||||
const userOrganizationId = user ? this.usersService.getUserMainOrgId(user) : null;
|
||||
return userOrganizationId;
|
||||
}
|
||||
|
||||
isMembershipAccepted(accepted: boolean) {
|
||||
if (!accepted) {
|
||||
throw new ForbiddenException(`User has not accepted membership in the organization.`);
|
||||
throw new ForbiddenException(
|
||||
`OrganizationRolesGuard - User has not accepted membership in the organization.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +69,9 @@ export class OrganizationRolesGuard implements CanActivate {
|
||||
|
||||
const hasRequiredRole = allowedRoles.includes(membershipRole);
|
||||
if (!hasRequiredRole) {
|
||||
throw new ForbiddenException(`User must have one of the roles: ${allowedRoles.join(", ")}.`);
|
||||
throw new ForbiddenException(
|
||||
`OrganizationRolesGuard - User must have one of the roles: ${allowedRoles.join(", ")}.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import { ManagedOrganizationsRepository } from "@/modules/organizations/organizations/managed-organizations.repository";
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class IsManagedOrgInManagerOrg implements CanActivate {
|
||||
constructor(private managedOrganizationsRepository: ManagedOrganizationsRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { managedOrganization: Team }>();
|
||||
const managedOrgId: string = request.params.managedOrganizationId;
|
||||
const managerOrgId: string = request.params.orgId;
|
||||
|
||||
if (!managerOrgId) {
|
||||
throw new ForbiddenException("No manager org id found in request params.");
|
||||
}
|
||||
|
||||
if (!managedOrgId) {
|
||||
throw new ForbiddenException("No managed org id found in request params.");
|
||||
}
|
||||
|
||||
const managedOrganization = await this.managedOrganizationsRepository.getByManagerManagedIds(
|
||||
Number(managerOrgId),
|
||||
Number(managedOrgId)
|
||||
);
|
||||
|
||||
if (!managedOrganization) {
|
||||
throw new NotFoundException(
|
||||
`Managed organization with id=${managedOrgId} within manager organization with id=${managerOrgId} not found.`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
|
||||
import { OrganizationsWebhooksRepository } from "@/modules/organizations/repositories/organizations-webhooks.repository";
|
||||
import { OrganizationsRepository } from "@/modules/organizations/index/organizations.repository";
|
||||
import { OrganizationsWebhooksRepository } from "@/modules/organizations/webhooks/organizations-webhooks.repository";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { RoutingFormsRepository } from "@/modules/routing-forms/routing-forms.repository";
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class IsRoutingFormInTeam implements CanActivate {
|
||||
constructor(private routingFormsRepository: RoutingFormsRepository) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<Request & { team: Team }>();
|
||||
const teamId: string = request.params.teamId;
|
||||
const routingFormId: string = request.params.routingFormId;
|
||||
|
||||
if (!routingFormId) {
|
||||
throw new ForbiddenException("No routing form id found in request params.");
|
||||
}
|
||||
|
||||
if (!teamId) {
|
||||
throw new ForbiddenException("No team id found in request params.");
|
||||
}
|
||||
|
||||
const routingForm = await this.routingFormsRepository.getTeamRoutingForm(Number(teamId), routingFormId);
|
||||
|
||||
if (!routingForm) {
|
||||
throw new NotFoundException(
|
||||
`Team with id=(${teamId}) routing form with id=(${routingFormId}) not found.`
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user