Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
235382213f | ||
|
|
01baf7d3c0 | ||
|
|
1a649158e1 | ||
|
|
d6306dcd22 | ||
|
|
7131a1f015 | ||
|
|
5357228f72 | ||
|
|
bfeafaa21f | ||
|
|
02ef6dc934 | ||
|
|
4424b2aea3 | ||
|
|
1f91fe888a | ||
|
|
b4a51d3560 | ||
|
|
4855b6b40f | ||
|
|
4b264a867b |
+8
-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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
diff --git a/index.cjs b/index.cjs
|
||||
index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67746ad5a4 100644
|
||||
index c83f700..da6fc7e 100644
|
||||
--- a/index.cjs
|
||||
+++ b/index.cjs
|
||||
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
|
||||
@@ -12,4 +12,4 @@ index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67
|
||||
+// exports['default'] = min.parsePhoneNumberFromString
|
||||
|
||||
// `parsePhoneNumberFromString()` named export is now considered legacy:
|
||||
// it has been promoted to a default export due to being too verbose.
|
||||
// it has been promoted to a default export due to being too verbose.
|
||||
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/prisma"
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
undecided:
|
||||
- calcom-monorepo
|
||||
- "@calcom/app-store-cli"
|
||||
- "@calcom/platform-constants"
|
||||
- "@calcom/platform-enums"
|
||||
- "@calcom/platform-types"
|
||||
- "@calcom/platform-utils"
|
||||
- "@calcom/prisma"
|
||||
Vendored
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();
|
||||
});
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
import type { RatelimitResponse } from "@unkey/ratelimit";
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiResponse, NextApiRequest } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
|
||||
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
const testUserId = 123;
|
||||
|
||||
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
|
||||
checkRateLimitAndThrowError: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/lib/autoLock", () => ({
|
||||
handleAutoLock: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("rateLimitApiKey middleware", () => {
|
||||
it("should return 401 if no apiKey is provided", async () => {
|
||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
query: {},
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
@@ -31,6 +41,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
||||
@@ -43,7 +54,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||
identifier: "test-key",
|
||||
identifier: testUserId.toString(),
|
||||
rateLimitingType: "api",
|
||||
onRateLimiterResponse: expect.any(Function),
|
||||
});
|
||||
@@ -53,19 +64,23 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse = {
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
limit: 100,
|
||||
remaining: 99,
|
||||
reset: Date.now(),
|
||||
success: true,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
});
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
@@ -79,6 +94,7 @@ describe("rateLimitApiKey middleware", () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
||||
@@ -89,4 +105,139 @@ describe("rateLimitApiKey middleware", () => {
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Rate limit exceeded" });
|
||||
});
|
||||
|
||||
it("should lock API key when rate limit is repeatedly exceeded", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to indicate the key was locked
|
||||
vi.mocked(handleAutoLock).mockResolvedValueOnce(true);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Too many requests" });
|
||||
});
|
||||
|
||||
it("should handle API key not found error during auto-lock", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to throw a "No user found" error
|
||||
vi.mocked(handleAutoLock).mockRejectedValueOnce(new Error("No user found for this API key."));
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
expect(res._getStatusCode()).toBe(401);
|
||||
expect(res._getJSONData()).toEqual({ message: "No user found for this API key." });
|
||||
});
|
||||
|
||||
it("should continue if auto-lock returns false (not locked)", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
const rateLimiterResponse: RatelimitResponse = {
|
||||
success: false,
|
||||
remaining: 0,
|
||||
limit: 100,
|
||||
reset: Date.now(),
|
||||
};
|
||||
|
||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||
onRateLimiterResponse(rateLimiterResponse);
|
||||
}
|
||||
);
|
||||
|
||||
// Mock handleAutoLock to indicate the key was not locked
|
||||
vi.mocked(handleAutoLock).mockResolvedValueOnce(false);
|
||||
|
||||
const next = vi.fn();
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, next);
|
||||
|
||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||
identifier: testUserId.toString(),
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: rateLimiterResponse,
|
||||
});
|
||||
|
||||
// Verify headers were set but request continued
|
||||
expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
|
||||
expect(next).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should handle HttpError during rate limiting", async () => {
|
||||
const { req, res } = createMocks({
|
||||
method: "GET",
|
||||
query: { apiKey: "test-key" },
|
||||
userId: testUserId,
|
||||
});
|
||||
|
||||
// Mock checkRateLimitAndThrowError to throw HttpError
|
||||
vi.mocked(checkRateLimitAndThrowError).mockRejectedValueOnce(
|
||||
new HttpError({
|
||||
statusCode: 429,
|
||||
message: "Custom rate limit error",
|
||||
})
|
||||
);
|
||||
|
||||
// @ts-expect-error weird typing between middleware and createMocks
|
||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||
|
||||
expect(res._getStatusCode()).toBe(429);
|
||||
expect(res._getJSONData()).toEqual({ message: "Custom rate limit error" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,23 +1,47 @@
|
||||
import type { NextMiddleware } from "next-api-middleware";
|
||||
|
||||
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
|
||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||
if (!req.userId) return res.status(401).json({ message: "No userId provided" });
|
||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||
|
||||
// TODO: Add a way to add trusted api keys
|
||||
try {
|
||||
const identifier = req.userId.toString();
|
||||
await checkRateLimitAndThrowError({
|
||||
identifier: req.query.apiKey as string,
|
||||
identifier,
|
||||
rateLimitingType: "api",
|
||||
onRateLimiterResponse: (response) => {
|
||||
onRateLimiterResponse: async (response) => {
|
||||
res.setHeader("X-RateLimit-Limit", response.limit);
|
||||
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||
res.setHeader("X-RateLimit-Reset", response.reset);
|
||||
|
||||
try {
|
||||
const didLock = await handleAutoLock({
|
||||
identifier,
|
||||
identifierType: "userId",
|
||||
rateLimitResponse: response,
|
||||
});
|
||||
|
||||
if (didLock) {
|
||||
return res.status(429).json({ message: "Too many requests" });
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === "No user found for this API key.") {
|
||||
return res.status(401).json({ message: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(429).json({ message: "Rate limit exceeded" });
|
||||
if (error instanceof HttpError) {
|
||||
return res.status(error.statusCode).json({ message: error.message });
|
||||
}
|
||||
return res.status(429).json({ message: "Rate limit exceeded" });
|
||||
}
|
||||
|
||||
await next();
|
||||
|
||||
@@ -38,6 +38,23 @@ export const schemaBookingGetParams = z.object({
|
||||
|
||||
export type Status = z.infer<typeof schemaBookingGetParams>["status"];
|
||||
|
||||
export const bookingCancelSchema = z.object({
|
||||
id: z.number(),
|
||||
allRemainingBookings: z.boolean().optional(),
|
||||
cancelSubsequentBookings: z.boolean().optional(),
|
||||
cancellationReason: z.string().optional().default("Not Provided"),
|
||||
seatReferenceUid: z.string().optional(),
|
||||
cancelledBy: z.string().email({ message: "Invalid email" }).optional(),
|
||||
internalNote: z
|
||||
.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
cancellationReason: z.string().optional().nullable(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
});
|
||||
|
||||
const schemaBookingEditParams = z
|
||||
.object({
|
||||
title: z.string().optional(),
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Helper schema for JSON fields
|
||||
type Literal = boolean | number | string;
|
||||
type Literal = boolean | number | string | null;
|
||||
type Json = Literal | { [key: string]: Json } | Json[];
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean()]);
|
||||
const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
|
||||
const jsonObjectSchema = z.record(z.lazy(() => jsonSchema));
|
||||
const jsonArraySchema = z.array(z.lazy(() => jsonSchema));
|
||||
export const jsonSchema: z.ZodSchema<Json> = z.lazy(() =>
|
||||
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
|
||||
z.union([literalSchema, jsonObjectSchema, jsonArraySchema])
|
||||
);
|
||||
|
||||
@@ -2,8 +2,8 @@ import type { NextApiRequest } from "next";
|
||||
|
||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { bookingCancelSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { bookingCancelSchema } from "~/lib/validations/booking";
|
||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||
|
||||
/**
|
||||
@@ -70,6 +70,7 @@ async function handler(req: NextApiRequest) {
|
||||
...req.query,
|
||||
allRemainingBookings: req.query.allRemainingBookings === "true",
|
||||
});
|
||||
|
||||
// Normalizing for universal handler
|
||||
req.body = { id, allRemainingBookings, cancellationReason };
|
||||
return await handleCancelBooking(req);
|
||||
|
||||
@@ -5,6 +5,7 @@ import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
|
||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
|
||||
@@ -213,6 +214,10 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
*/
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||
req.body = {
|
||||
...req.body,
|
||||
creationSource: CreationSource.API_V1,
|
||||
};
|
||||
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||
|
||||
if (isOrganizationOwnerOrAdmin) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { HttpError } from "@calcom/lib/http-error";
|
||||
import { defaultResponder } from "@calcom/lib/server";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { CreationSource } from "@calcom/prisma/enums";
|
||||
import { createContext } from "@calcom/trpc/server/createContext";
|
||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||
@@ -43,6 +44,7 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
language: data.language,
|
||||
teamId: data.teamId,
|
||||
usernameOrEmail: data.usernameOrEmail,
|
||||
creationSource: CreationSource.API_V1,
|
||||
});
|
||||
|
||||
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||
|
||||
@@ -2,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.85",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.99",
|
||||
"@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",
|
||||
|
||||
@@ -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❗_
|
||||
|
||||
@@ -8,6 +8,7 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
@@ -18,6 +19,7 @@ import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repo
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||
@@ -35,7 +37,7 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let apiKeyString: string;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `bookings-2024-04-15-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
@@ -67,13 +69,17 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||
apiKeyString = keyString;
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `bookings-2024-04-15-schedule-${randomString()}-${describe.name}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `bookings-2024-04-15-event-type-${randomString()}-${describe.name}`,
|
||||
slug: `bookings-2024-04-15-event-type-${randomString()}-${describe.name}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
@@ -343,94 +349,6 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// note(Lauris) : found this test broken here - first thing to fix is that recurring endpoint accepts an array not 1 object.
|
||||
// it("should create a recurring booking", async () => {
|
||||
// const bookingStart = "2040-05-25T09:30:00.000Z";
|
||||
// const bookingEnd = "2040-05-25T10:30:00.000Z";
|
||||
// const bookingEventTypeId = 7;
|
||||
// const bookingTimeZone = "Europe/London";
|
||||
// const bookingLanguage = "en";
|
||||
// const bookingHashedLink = "";
|
||||
// const bookingRecurringCount = 5;
|
||||
// const currentBookingRecurringIndex = 0;
|
||||
|
||||
// const body = {
|
||||
// start: bookingStart,
|
||||
// end: bookingEnd,
|
||||
// eventTypeId: bookingEventTypeId,
|
||||
// timeZone: bookingTimeZone,
|
||||
// language: bookingLanguage,
|
||||
// metadata: {},
|
||||
// hashedLink: bookingHashedLink,
|
||||
// recurringCount: bookingRecurringCount,
|
||||
// currentRecurringIndex: currentBookingRecurringIndex,
|
||||
// };
|
||||
|
||||
// return request(app.getHttpServer())
|
||||
// .post("/v2/bookings/recurring")
|
||||
// .send(body)
|
||||
// .expect(201)
|
||||
// .then((response) => {
|
||||
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleNewRecurringBooking>>> =
|
||||
// response.body;
|
||||
|
||||
// expect(responseBody.status).toEqual("recurring");
|
||||
// });
|
||||
// });
|
||||
|
||||
// note(Lauris) : found this test broken here - first thing to fix is that the eventTypeId must be team event type, because
|
||||
// instant bookings only work for teams.
|
||||
// it("should create an instant booking", async () => {
|
||||
// const bookingStart = "2040-05-25T09:30:00.000Z";
|
||||
// const bookingEnd = "2040-25T10:30:00.000Z";
|
||||
// const bookingEventTypeId = 7;
|
||||
// const bookingTimeZone = "Europe/London";
|
||||
// const bookingLanguage = "en";
|
||||
// const bookingHashedLink = "";
|
||||
|
||||
// const body = {
|
||||
// start: bookingStart,
|
||||
// end: bookingEnd,
|
||||
// eventTypeId: bookingEventTypeId,
|
||||
// timeZone: bookingTimeZone,
|
||||
// language: bookingLanguage,
|
||||
// metadata: {},
|
||||
// hashedLink: bookingHashedLink,
|
||||
// };
|
||||
|
||||
// return request(app.getHttpServer())
|
||||
// .post("/v2/bookings/instant")
|
||||
// .send(body)
|
||||
// .expect(201)
|
||||
// .then((response) => {
|
||||
// const responseBody: ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>> = response.body;
|
||||
|
||||
// expect(responseBody.status).toEqual("instant");
|
||||
// });
|
||||
// });
|
||||
|
||||
// cancelling a booking hangs the test for some reason
|
||||
it.skip("should cancel a booking", async () => {
|
||||
const bookingId = createdBooking.id;
|
||||
|
||||
const body = {
|
||||
allRemainingBookings: false,
|
||||
cancellationReason: "Was fighting some unforseen rescheduling demons",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/bookings/${bookingId}/cancel`)
|
||||
.send(body)
|
||||
.expect(201)
|
||||
.then((response) => {
|
||||
const responseBody: ApiResponse<{ status: typeof SUCCESS_STATUS | typeof ERROR_STATUS }> =
|
||||
response.body;
|
||||
|
||||
expect(bookingId).toBeDefined();
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||
import { User } from "@prisma/client";
|
||||
import { CreationSource } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { NextApiRequest } from "next/types";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -383,7 +384,11 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
});
|
||||
Object.assign(clone, { userId, ...oAuthParams, platformBookingLocation });
|
||||
clone.body = { ...clone.body, noEmail: !oAuthParams.arePlatformEmailsEnabled };
|
||||
clone.body = {
|
||||
...clone.body,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -411,6 +416,7 @@ export class BookingsController_2024_04_15 {
|
||||
...oAuthParams,
|
||||
platformBookingLocation,
|
||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
});
|
||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
||||
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
@@ -126,4 +126,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;
|
||||
}
|
||||
|
||||
@@ -32,5 +32,6 @@ import { Module } from "@nestjs/common";
|
||||
ApiKeyRepository,
|
||||
],
|
||||
controllers: [BookingsController_2024_08_13],
|
||||
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
|
||||
})
|
||||
export class BookingsModule_2024_08_13 {}
|
||||
|
||||
+12
-4
@@ -19,6 +19,7 @@ import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.rep
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -72,10 +73,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||
let apiKeyString: string;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `api-key-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = `api-key-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking: BookingOutput_2024_08_13;
|
||||
let rescheduledBooking: BookingOutput_2024_08_13;
|
||||
@@ -100,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `api-key-bookings-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -110,13 +114,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
apiKeyString = keyString;
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `api-key-bookings-e2e-api-key-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `api-key-bookings-e2e-api-key-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: eventTypeSlug,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
+122
-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: [
|
||||
{
|
||||
@@ -381,11 +313,93 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeWithBookingFieldsId = eventTypeWithBookingFields.id;
|
||||
|
||||
bookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 12, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 13, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
seatedBookingWithSplitName = await bookingsRepositoryFixture.create({
|
||||
user: {
|
||||
connect: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
startTime: new Date(Date.UTC(2020, 0, 8, 14, 0, 0)),
|
||||
endTime: new Date(Date.UTC(2020, 0, 8, 15, 0, 0)),
|
||||
title: "peer coding lets goo",
|
||||
uid: "seated-booking",
|
||||
eventType: {
|
||||
connect: {
|
||||
id: seatedEvent.id,
|
||||
},
|
||||
},
|
||||
location: "integrations:daily",
|
||||
customInputs: {},
|
||||
metadata: {},
|
||||
responses: {
|
||||
name: splitName,
|
||||
email: "oldie@gmail.com",
|
||||
},
|
||||
attendees: {
|
||||
create: {
|
||||
email: "oldie@gmail.com",
|
||||
name: "Oldie Goldie",
|
||||
locale: "lv",
|
||||
timeZone: "Europe/Rome",
|
||||
bookingSeat: {
|
||||
create: {
|
||||
referenceUid: "unique-seat-uid",
|
||||
data: {
|
||||
responses: {
|
||||
email: "oldie@gmail.com",
|
||||
name: {
|
||||
firstName: "Oldie",
|
||||
lastName: "Goldie",
|
||||
},
|
||||
},
|
||||
},
|
||||
metadata: {},
|
||||
booking: {
|
||||
connect: {
|
||||
uid: "seated-booking",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
@@ -441,7 +455,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 +470,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 +489,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
it("should be able to book an event type with custom required booking fields", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: eventTypeWithBookingFieldsId,
|
||||
eventTypeId: eventTypeWithBookingFields.id,
|
||||
attendee: {
|
||||
name: "Mr Proper",
|
||||
email: "mr_proper@gmail.com",
|
||||
|
||||
+12
-5
@@ -16,6 +16,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -35,11 +36,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `confirm-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
let eventTypeId: number;
|
||||
const eventTypeSlug = "peer-coding";
|
||||
const eventTypeSlug = `confirm-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
|
||||
let createdBooking1: Booking;
|
||||
let createdBooking2: Booking;
|
||||
@@ -64,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -77,13 +80,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-bookings-2024-08-13-${randomString()}-schedule`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: eventTypeSlug, length: 60 },
|
||||
{
|
||||
title: `confirm-bookings-2024-08-13-${randomString()}-event-type`,
|
||||
slug: `confirm-bookings-2024-08-13-${randomString()}-event-type-slug`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
eventTypeId = event.id;
|
||||
|
||||
+21
-8
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -92,7 +93,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = `admin-${Math.floor(Math.random() * 1000)}@example.com`;
|
||||
const authEmail = `confirm-emails-2024-08-13-admin-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -114,7 +115,9 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `confirm-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -139,7 +142,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -148,14 +151,19 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
@@ -171,7 +179,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `confirm-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -179,13 +187,18 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60, requiresConfirmation: true },
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: `confirm-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
requiresConfirmation: true,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
|
||||
+23
-20
@@ -23,6 +23,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -103,7 +104,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
let emailsEnabledSetup: EmailSetup;
|
||||
let emailsDisabledSetup: EmailSetup;
|
||||
|
||||
const authEmail = "admin@example.com";
|
||||
const authEmail = "team-emails-2024-08-13-user-admin@example.com";
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -128,7 +129,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({ name: `team-emails-organization-${randomString()}` });
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -152,7 +153,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `team-emails-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -163,7 +164,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "alice@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -172,7 +173,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "bob@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -181,7 +182,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -237,8 +238,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -278,8 +279,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -336,7 +337,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const team = await teamRepositoryFixture.create({
|
||||
name: "team 2",
|
||||
name: `team-emails-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -347,7 +348,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member1 = await userRepositoryFixture.create({
|
||||
email: "charlie@gmail.com",
|
||||
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -356,7 +357,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const member2 = await userRepositoryFixture.create({
|
||||
email: "dean@gmail.com",
|
||||
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -365,7 +366,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -421,8 +422,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
@@ -462,8 +463,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
team: {
|
||||
connect: { id: team.id },
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
@@ -965,17 +966,19 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await userRepositoryFixture.deleteByEmail(authEmail);
|
||||
await userRepositoryFixture.deleteByEmail(emailsEnabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member1.email);
|
||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member2.email);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsEnabledSetup.member1.id,
|
||||
emailsEnabledSetup.member1.email
|
||||
);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(
|
||||
emailsDisabledSetup.member1.id,
|
||||
emailsDisabledSetup.member1.email
|
||||
emailsDisabledSetup.member2.email
|
||||
);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
+22
-11
@@ -20,6 +20,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -101,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `user-emails-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
await setupEnabledEmails();
|
||||
await setupDisabledEmails();
|
||||
@@ -116,7 +119,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `alice-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsEnabled.id,
|
||||
@@ -125,22 +128,26 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
@@ -160,7 +167,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||
|
||||
const user = await userRepositoryFixture.create({
|
||||
email: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
||||
email: `user-emails-2024-08-13-user-${randomString()}@api.com`,
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClientEmailsDisabled.id,
|
||||
@@ -168,21 +175,25 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
||||
},
|
||||
});
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const event = await eventTypesRepositoryFixture.create(
|
||||
{ title: "peer coding", slug: "peer-coding", length: 60 },
|
||||
{
|
||||
title: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||
},
|
||||
|
||||
+12
-9
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -45,8 +46,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const teamUserEmail = "orgUser1team1@api.com";
|
||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
||||
const teamUserEmail = `reassign-bookings-2024-08-13-user1-${randomString()}@api.com`;
|
||||
const teamUserEmail2 = `reassign-bookings-2024-08-13-user2-${randomString()}@api.com`;
|
||||
let teamUser1: User;
|
||||
let teamUser2: User;
|
||||
|
||||
@@ -78,11 +79,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization team bookings" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `reassign-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
team = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
name: `reassign-bookings-2024-08-13-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
@@ -95,17 +98,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamUser1 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail,
|
||||
locale: "it",
|
||||
name: "orgUser1team1",
|
||||
name: `reassign-bookings-2024-08-13-user1-${randomString()}`,
|
||||
});
|
||||
|
||||
teamUser2 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail2,
|
||||
locale: "it",
|
||||
name: "orgUser2team1",
|
||||
name: `reassign-bookings-2024-08-13-user2-${randomString()}`,
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `reassign-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -164,8 +167,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
users: {
|
||||
connect: [{ id: teamUser1.id }, { id: teamUser2.id }],
|
||||
},
|
||||
title: "Round Robin Event Type",
|
||||
slug: "round-robin-event-type",
|
||||
title: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
assignAllTeamMembers: false,
|
||||
bookingFields: [],
|
||||
|
||||
+26
-15
@@ -17,6 +17,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -41,11 +42,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 3;
|
||||
let recurringEventTypeId: number;
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
let createdRecurringBooking: RecurringBookingOutput_2024_08_13[];
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -67,7 +70,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -80,7 +85,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
@@ -89,8 +94,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -344,7 +349,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -380,7 +385,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -393,17 +400,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
@@ -551,7 +559,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "bookings-controller-e2e@api.com";
|
||||
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const maxRecurrenceCount = 4;
|
||||
@@ -587,7 +595,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `recurring-bookings-2024-08-13-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -600,17 +610,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
|
||||
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: "peer-coding-recurring",
|
||||
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: recurringEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
},
|
||||
|
||||
+45
-44
@@ -19,6 +19,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -51,22 +52,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_seated@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper Seated";
|
||||
const emailAttendeeTwo = "mr_proper_friend_seated@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Friend Seated";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -88,7 +90,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -101,15 +105,15 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
@@ -123,8 +127,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
@@ -200,7 +204,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
@@ -272,7 +276,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(2);
|
||||
// note(Lauris): first attendee is from previous test request
|
||||
@@ -366,7 +370,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -401,7 +405,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -575,7 +579,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
const attendee = createdSeatedBooking.attendees.find((a) => a.seatUid === body.seatUid);
|
||||
@@ -629,7 +633,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||
expect(data.eventType).toEqual({
|
||||
id: seatedEventTypeId,
|
||||
slug: seatedTventTypeSlug,
|
||||
slug: seatedEventSlug,
|
||||
});
|
||||
expect(data.attendees.length).toEqual(0);
|
||||
expect(data.location).toBeDefined();
|
||||
@@ -681,22 +685,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
|
||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
||||
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
const seatedTventTypeSlug = "peer-coding-seated";
|
||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
||||
let seatedEventTypeId: number;
|
||||
let recurringSeatedEventTypeId: number;
|
||||
const maxRecurrenceCount = 3;
|
||||
|
||||
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||
|
||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||
|
||||
const emailAttendeeOne = "mr_proper_first@gmail.com";
|
||||
const nameAttendeeOne = "Mr Proper First";
|
||||
const emailAttendeeTwo = "mr_proper_second@gmail.com";
|
||||
const nameAttendeeTwo = "Mr Proper Second";
|
||||
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
@@ -718,7 +723,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization bookings" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `seated-bookings-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
@@ -731,20 +738,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||
{
|
||||
title: "peer coding",
|
||||
slug: seatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||
slug: seatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
seatsPerTimeSlot: 3,
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -753,14 +757,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
||||
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||
{
|
||||
title: "peer coding recurring",
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||
slug: recurringSeatedEventSlug,
|
||||
length: 60,
|
||||
seatsPerTimeSlot: 3,
|
||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||
seatsPerTimeSlot: 5,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: true,
|
||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
||||
},
|
||||
user.id
|
||||
);
|
||||
@@ -836,7 +837,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(1);
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
@@ -871,7 +872,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(1);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
@@ -949,7 +950,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(firstBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(firstBooking.attendees.length).toEqual(2);
|
||||
const firstAttendee = firstBooking.attendees.find(
|
||||
@@ -1003,7 +1004,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||
expect(secondBooking.eventType).toEqual({
|
||||
id: recurringSeatedEventTypeId,
|
||||
slug: recurringSeatedTventTypeSlug,
|
||||
slug: recurringSeatedEventSlug,
|
||||
});
|
||||
expect(secondBooking.attendees.length).toEqual(2);
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
|
||||
@@ -21,6 +21,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
@@ -52,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
|
||||
);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.se
|
||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { CreationSource } from "@prisma/client";
|
||||
import { Request } from "express";
|
||||
import { DateTime } from "luxon";
|
||||
import { NextApiRequest } from "next/types";
|
||||
@@ -97,10 +98,14 @@ export class InputBookingsService_2024_08_13 {
|
||||
|
||||
if (oAuthClientParams) {
|
||||
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: !oAuthClientParams.arePlatformEmailsEnabled };
|
||||
newRequest.body = {
|
||||
...bodyTransformed,
|
||||
noEmail: !oAuthClientParams.arePlatformEmailsEnabled,
|
||||
creationSource: CreationSource.API_V2,
|
||||
};
|
||||
} else {
|
||||
Object.assign(newRequest, { userId, platformBookingLocation: location });
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false };
|
||||
newRequest.body = { ...bodyTransformed, noEmail: false, creationSource: CreationSource.API_V2 };
|
||||
}
|
||||
|
||||
return newRequest as unknown as BookingRequest;
|
||||
@@ -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 })
|
||||
|
||||
+37
-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",
|
||||
});
|
||||
|
||||
@@ -1221,7 +1237,9 @@ describe("Event types Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `event-types-2024-06-14-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
@@ -1708,7 +1726,9 @@ describe("Event types Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `event-types-2024-06-14-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
|
||||
@@ -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,27 @@ 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;
|
||||
}
|
||||
|
||||
+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,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { MockedRedisService } from "test/mocks/mock-redis-service";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
|
||||
@@ -39,9 +40,9 @@ describe("ApiAuthStrategy", () => {
|
||||
let oAuthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let profilesRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const validApiKeyEmail = "api-key-user-email@example.com";
|
||||
const validAccessTokenEmail = "access-token-user-email@example.com";
|
||||
const validOAuthEmail = "oauth-user@example.com";
|
||||
const validApiKeyEmail = `api-auth-api-key-user-${randomString()}@api.com`;
|
||||
const validAccessTokenEmail = `api-auth-access-token-user-${randomString()}@api.com`;
|
||||
const validOAuthEmail = `api-auth-oauth-user-${randomString()}@api.com`;
|
||||
|
||||
let validApiKeyUser: User;
|
||||
let validAccessTokenUser: User;
|
||||
@@ -83,7 +84,7 @@ describe("ApiAuthStrategy", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(module);
|
||||
oAuthClientRepositoryFixture = new OAuthClientRepositoryFixture(module);
|
||||
profilesRepositoryFixture = new ProfileRepositoryFixture(module);
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({ name: `api-auth-organization-${randomString()}` });
|
||||
validApiKeyUser = await userRepositoryFixture.create({
|
||||
email: validApiKeyEmail,
|
||||
});
|
||||
|
||||
@@ -19,6 +19,8 @@ import { INVALID_ACCESS_TOKEN, X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom
|
||||
|
||||
export type ApiAuthGuardUser = UserWithProfile & { isSystemAdmin: boolean };
|
||||
|
||||
export const NO_AUTH_PROVIDED_MESSAGE =
|
||||
"No authentication method provided. Either pass an API key as 'Bearer' header or OAuth client credentials as 'x-cal-secret-key' and 'x-cal-client-id' headers";
|
||||
@Injectable()
|
||||
export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth") {
|
||||
constructor(
|
||||
@@ -62,9 +64,7 @@ export class ApiAuthStrategy extends PassportStrategy(BaseStrategy, "api-auth")
|
||||
return await this.authenticateNextAuth(nextAuthToken);
|
||||
}
|
||||
|
||||
throw new UnauthorizedException(
|
||||
"No authentication method provided. Either pass an API key as 'Bearer' header or OAuth client credentials as 'x-cal-secret-key' and 'x-cal-client-id' headers"
|
||||
);
|
||||
throw new UnauthorizedException(NO_AUTH_PROVIDED_MESSAGE);
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return this.error(err);
|
||||
|
||||
@@ -16,13 +16,14 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { Team, PlatformOAuthClient, PlatformBilling } from "@calcom/prisma/client";
|
||||
|
||||
describe("Platform Billing Controller (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "platform-billing-controller-e2e@api.com";
|
||||
const userEmail = `billing-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let billing: PlatformBilling;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
@@ -47,7 +48,9 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
platformBillingRepositoryFixture = new PlatformBillingRepositoryFixture(moduleRef);
|
||||
organization = await organizationsRepositoryFixture.create({ name: "platform billing" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `billing-organization-${randomString()}`,
|
||||
});
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppsRepository } from "@/modules/apps/apps.repository";
|
||||
import { ConferencingController } from "@/modules/conferencing/controllers/conferencing.controller";
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository";
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
||||
import { ConferencingService } from "@/modules/conferencing/services/conferencing.service";
|
||||
import { GoogleMeetService } from "@/modules/conferencing/services/google-meet.service";
|
||||
import { Office365VideoService } from "@/modules/conferencing/services/office365-video.service";
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
@@ -30,7 +31,7 @@ describe("Conferencing Endpoints", () => {
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let credentialsRepositoryFixture: CredentialsRepositoryFixture;
|
||||
|
||||
const userEmail = "conferencing-controller-user-e2e@api.com";
|
||||
const userEmail = `conferencing-user-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository";
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.respository";
|
||||
import { ConferencingRepository } from "@/modules/conferencing/repositories/conferencing.repository";
|
||||
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||
|
||||
+8
-2
@@ -17,6 +17,7 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -55,9 +56,14 @@ describe("Platform Destination Calendar Endpoints", () => {
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
|
||||
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: `destination-calendars-organization-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser("office365-connect@gmail.com", oAuthClient.id);
|
||||
user = await userRepositoryFixture.createOAuthManagedUser(
|
||||
`destination-calendars-user-${randomString()}@api.com`,
|
||||
oAuthClient.id
|
||||
);
|
||||
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id);
|
||||
accessTokenSecret = tokens.accessToken;
|
||||
refreshTokenSecret = tokens.refreshToken;
|
||||
|
||||
@@ -4,6 +4,8 @@ import { BillingModule } from "@/modules/billing/billing.module";
|
||||
import { ConferencingModule } from "@/modules/conferencing/conferencing.module";
|
||||
import { DestinationCalendarsModule } from "@/modules/destination-calendars/destination-calendars.module";
|
||||
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
|
||||
import { OrganizationsTeamsBookingsModule } from "@/modules/organizations/controllers/teams/bookings/organizations-teams-bookings.module";
|
||||
import { RouterModule } from "@/modules/router/router.module";
|
||||
import { StripeModule } from "@/modules/stripe/stripe.module";
|
||||
import { TimezoneModule } from "@/modules/timezones/timezones.module";
|
||||
import type { MiddlewareConsumer, NestModule } from "@nestjs/common";
|
||||
@@ -24,6 +26,8 @@ import { WebhooksModule } from "./webhooks/webhooks.module";
|
||||
AtomsModule,
|
||||
StripeModule,
|
||||
ConferencingModule,
|
||||
OrganizationsTeamsBookingsModule,
|
||||
RouterModule,
|
||||
],
|
||||
})
|
||||
export class EndpointsModule implements NestModule {
|
||||
|
||||
+7
-6
@@ -17,13 +17,14 @@ import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { EventType, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("EventTypes WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "event-types-webhook-controller-e2e@api.com";
|
||||
const userEmail = `event-types-webhooks-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let otherUser: UserWithProfile;
|
||||
let eventType: EventType;
|
||||
@@ -55,14 +56,14 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
});
|
||||
|
||||
otherUser = await userRepositoryFixture.create({
|
||||
email: "other-user-webhook-controller@api.com",
|
||||
username: "other-user-webhook-controller@api.com",
|
||||
email: `event-types-webhooks-other-user-${randomString()}@api.com`,
|
||||
username: `event-types-webhooks-other-user-${randomString()}@api.com`,
|
||||
});
|
||||
|
||||
eventType = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Event Type 1",
|
||||
slug: "webhook-event-type-1",
|
||||
slug: `event-types-webhooks-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
@@ -71,7 +72,7 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
eventType2 = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Event Type 2",
|
||||
slug: "webhook-event-type-2",
|
||||
slug: `event-types-webhooks-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
user.id
|
||||
@@ -80,7 +81,7 @@ describe("EventTypes WebhooksController (e2e)", () => {
|
||||
otherEventType = await eventTypeRepositoryFixture.create(
|
||||
{
|
||||
title: "Other Event Type ",
|
||||
slug: "other-webhook-event-type",
|
||||
slug: `event-types-webhooks-other-event-type-${randomString()}`,
|
||||
length: 60,
|
||||
},
|
||||
otherUser.id
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { MembershipsService } from "@/modules/memberships/services/memberships.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
providers: [MembershipsRepository],
|
||||
exports: [MembershipsRepository],
|
||||
providers: [MembershipsRepository, MembershipsService],
|
||||
exports: [MembershipsRepository, MembershipsService],
|
||||
})
|
||||
export class MembershipsModule {}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { intersectionBy } from "lodash";
|
||||
|
||||
@Injectable()
|
||||
export class MembershipsService {
|
||||
constructor(private readonly membershipsRepository: MembershipsRepository) {}
|
||||
|
||||
async haveMembershipsInCommon(firstUserId: number, secondUserId: number) {
|
||||
const memberships = await this.membershipsInCommon(firstUserId, secondUserId);
|
||||
return memberships.length > 0;
|
||||
}
|
||||
|
||||
async membershipsInCommon(firstUserId: number, secondUserId: number) {
|
||||
const firstUserMemberships = await this.membershipsRepository.findUserMemberships(firstUserId);
|
||||
const secondUserMemberships = await this.membershipsRepository.findUserMemberships(secondUserId);
|
||||
|
||||
return intersectionBy(
|
||||
firstUserMemberships.filter((m) => m.accepted),
|
||||
secondUserMemberships.filter((m) => m.accepted),
|
||||
"teamId"
|
||||
);
|
||||
}
|
||||
}
|
||||
+16
-9
@@ -22,6 +22,7 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
@@ -84,10 +85,10 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
|
||||
let postResponseData: CreateManagedUserOutput["data"];
|
||||
|
||||
const platformAdminEmail = "platform-sensei@mail.com";
|
||||
const platformAdminEmail = `oauth-client-users-admin-${randomString()}@api.com`;
|
||||
let platformAdmin: User;
|
||||
|
||||
const userEmail = "oauth-client-user@gmail.com";
|
||||
const userEmail = `oauth-client-users-user-${randomString()}@api.com`;
|
||||
const userTimeZone = "Europe/Rome";
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -110,7 +111,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
platformAdmin = await userRepositoryFixture.create({ email: platformAdminEmail });
|
||||
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: "organization",
|
||||
name: `oauth-client-users-organization-${randomString()}`,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
});
|
||||
@@ -220,6 +221,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
await userConnectedToOAuth(responseBody.data.user.email);
|
||||
await userHasDefaultEventTypes(responseBody.data.user.id);
|
||||
await userHasDefaultSchedule(responseBody.data.user.id, responseBody.data.user.defaultScheduleId);
|
||||
await userHasOnlyOneSchedule(responseBody.data.user.id);
|
||||
});
|
||||
|
||||
async function userConnectedToOAuth(userEmail: string) {
|
||||
@@ -260,6 +262,11 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
expect(schedule?.userId).toEqual(userId);
|
||||
}
|
||||
|
||||
async function userHasOnlyOneSchedule(userId: number) {
|
||||
const schedules = await schedulesRepositoryFixture.getByUserId(userId);
|
||||
expect(schedules?.length).toEqual(1);
|
||||
}
|
||||
|
||||
it(`should fail /POST using already used managed user email`, async () => {
|
||||
const requestBody: CreateManagedUserInput = {
|
||||
email: userEmail,
|
||||
@@ -386,7 +393,7 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let postResponseData: CreateManagedUserOutput["data"];
|
||||
|
||||
const userEmail = "oauth-client-users-user@gmail.com";
|
||||
const userEmail = `oauth-client-users-user-${randomString()}@api.com`;
|
||||
const userTimeZone = "Europe/Rome";
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -405,20 +412,20 @@ describe("OAuth Client Users Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
organization = await teamRepositoryFixture.create({
|
||||
name: "Testy Organization",
|
||||
isOrganization: true,
|
||||
name: `oauth-client-users-organization-${randomString()}`,
|
||||
isPlatform: true,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
owner = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
email: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
username: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
organization: { connect: { id: organization.id } },
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${owner.id}`,
|
||||
username: userEmail,
|
||||
username: `oauth-client-users-admin-${randomString()}@api.com`,
|
||||
organization: {
|
||||
connect: {
|
||||
id: organization.id,
|
||||
|
||||
+5
-4
@@ -20,14 +20,15 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { PlatformOAuthClient, Team, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("OAuth client WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
const userEmail = "oauth-client-webhook-controller-e2e@api.com";
|
||||
const otherUserEmail = "other-oauth-client-webhook-controller-e2e@api.com";
|
||||
const userEmail = `oauth-client-webhooks-user-${randomString()}@api.com`;
|
||||
const otherUserEmail = `oauth-client-webhooks-other-user-${randomString()}@api.com`;
|
||||
let user: UserWithProfile;
|
||||
let otherUser: UserWithProfile;
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
@@ -69,7 +70,7 @@ describe("OAuth client WebhooksController (e2e)", () => {
|
||||
});
|
||||
|
||||
org = await orgRepositoryFixture.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-client-webhooks-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -80,7 +81,7 @@ describe("OAuth client WebhooksController (e2e)", () => {
|
||||
isPlatform: true,
|
||||
});
|
||||
otherOrg = await orgRepositoryFixture.create({
|
||||
name: "otherOrg",
|
||||
name: `oauth-client-webhooks-other-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
|
||||
+9
-8
@@ -18,6 +18,7 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { NextAuthMockStrategy } from "test/mocks/next-auth-mock.strategy";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -68,7 +69,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let app: INestApplication;
|
||||
const userEmail = "oauth-clients-test-e2e@api.com";
|
||||
const userEmail = `oauth-clients-user-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withNextAuth(
|
||||
@@ -89,7 +90,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
org = await teamFixtures.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-clients-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -126,7 +127,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let app: INestApplication;
|
||||
const userEmail = "test-e2e@api.com";
|
||||
const userEmail = `oauth-clients-user-${randomString()}@api.com`;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withNextAuth(
|
||||
@@ -147,7 +148,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
org = await teamFixtures.create({
|
||||
name: "apiOrg",
|
||||
name: `oauth-clients-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
metadata: {
|
||||
isOrganization: true,
|
||||
@@ -211,7 +212,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
describe("User is part of an organization as Admin", () => {
|
||||
let membership: Membership;
|
||||
let client: { clientId: string; clientSecret: string };
|
||||
const oAuthClientName = "test-oauth-client-admin";
|
||||
const oAuthClientName = `oauth-clients-admin-${randomString()}`;
|
||||
|
||||
beforeAll(async () => {
|
||||
membership = await membershipFixtures.addUserToOrg(user, org, "ADMIN", true);
|
||||
@@ -264,7 +265,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
});
|
||||
});
|
||||
it(`/PUT/:id`, () => {
|
||||
const clientUpdatedName = "test-oauth-client-updated";
|
||||
const clientUpdatedName = `oauth-clients-admin-updated-${randomString()}`;
|
||||
const body: UpdateOAuthClientInput = { name: clientUpdatedName };
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/oauth-clients/${client.clientId}`)
|
||||
@@ -289,7 +290,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
describe("User is part of an organization as Owner", () => {
|
||||
let membership: Membership;
|
||||
let client: { clientId: string; clientSecret: string };
|
||||
const oAuthClientName = "test-oauth-client-owner";
|
||||
const oAuthClientName = `oauth-clients-owner-${randomString()}`;
|
||||
const oAuthClientPermissions = 32;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -346,7 +347,7 @@ describe("OAuth Clients Endpoints", () => {
|
||||
});
|
||||
});
|
||||
it(`/PUT/:id`, () => {
|
||||
const clientUpdatedName = "test-oauth-client-updated";
|
||||
const clientUpdatedName = `oauth-clients-owner-updated-${randomString()}`;
|
||||
const body: UpdateOAuthClientInput = { name: clientUpdatedName };
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/api/v2/oauth-clients/${client.clientId}`)
|
||||
|
||||
+5
-2
@@ -19,6 +19,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
@@ -70,7 +71,7 @@ describe("OAuthFlow Endpoints", () => {
|
||||
let refreshToken: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
const userEmail = "developer@platform.com";
|
||||
const userEmail = `oauth-flow-user-${randomString()}@api.com`;
|
||||
|
||||
const moduleRef: TestingModule = await withNextAuth(
|
||||
userEmail,
|
||||
@@ -92,7 +93,9 @@ describe("OAuthFlow Endpoints", () => {
|
||||
email: userEmail,
|
||||
});
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization" });
|
||||
organization = await organizationsRepositoryFixture.create({
|
||||
name: `oauth-flow-organization-${randomString()}`,
|
||||
});
|
||||
await profilesRepositoryFixture.create({
|
||||
uid: "asd-asd",
|
||||
username: userEmail,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-us
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
import { BadRequestException, ConflictException, Injectable } from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import { User, CreationSource } from "@prisma/client";
|
||||
|
||||
import { createNewUsersConnectToOrgIfExists, slugify } from "@calcom/platform-libraries";
|
||||
|
||||
@@ -46,6 +46,7 @@ export class OAuthClientUsersService {
|
||||
role: "MEMBER",
|
||||
},
|
||||
],
|
||||
creationSource: CreationSource.API_V2,
|
||||
teamId: organizationId,
|
||||
isOrg: true,
|
||||
parentId: null,
|
||||
|
||||
+5
-4
@@ -14,6 +14,7 @@ import { AttributeRepositoryFixture } from "test/fixtures/repository/attributes.
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -26,7 +27,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "member@attributes-options-api.com";
|
||||
const userEmail = `organization-attributes-options-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -45,7 +46,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesOptionsCorp",
|
||||
name: `organization-attributes-options-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -97,7 +98,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let attributeRepositoryFixture: AttributeRepositoryFixture;
|
||||
|
||||
const userEmail = "admin@attributes-options-api.com";
|
||||
const userEmail = `organization-attributes-options-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -123,7 +124,7 @@ describe("Organizations Attributes Options Endpoints", () => {
|
||||
attributeRepositoryFixture = new AttributeRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesOptionsCorp",
|
||||
name: `organization-attributes-options-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+5
-4
@@ -12,6 +12,7 @@ import * as request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -24,7 +25,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "member@attributes-api.com";
|
||||
const userEmail = `organization-attributes-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -42,7 +43,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesCorp",
|
||||
name: `organization-attributes-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -88,7 +89,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "admin@attributes-api.com";
|
||||
const userEmail = `organization-attributes-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -116,7 +117,7 @@ describe("Organizations Attributes Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "AttributesCorp",
|
||||
name: `organization-attributes-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+6
-2
@@ -149,10 +149,14 @@ export class OrganizationsEventTypesController {
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Query() queryParams: GetTeamEventTypesQuery_2024_06_14
|
||||
): Promise<GetTeamEventTypesOutput> {
|
||||
const { eventSlug } = queryParams;
|
||||
const { eventSlug, hostsLimit } = queryParams;
|
||||
|
||||
if (eventSlug) {
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(teamId, eventSlug);
|
||||
const eventType = await this.organizationsEventTypesService.getTeamEventTypeBySlug(
|
||||
teamId,
|
||||
eventSlug,
|
||||
hostsLimit
|
||||
);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
|
||||
+12
-11
@@ -14,6 +14,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -48,12 +49,12 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
let falseTestOrg: Team;
|
||||
let falseTestTeam: Team;
|
||||
|
||||
const userEmail = "org-admin-event-types-controller-e222e@api.com";
|
||||
const userEmail = `organizations-event-types-admin-${randomString()}@api.com`;
|
||||
let userAdmin: User;
|
||||
|
||||
const teammate1Email = "teammate111@team.com";
|
||||
const teammate2Email = "teammate221@team.com";
|
||||
const falseTestUserEmail = "false-user@false-team.com";
|
||||
const teammate1Email = `organizations-event-types-teammate1-${randomString()}@api.com`;
|
||||
const teammate2Email = `organizations-event-types-teammate2-${randomString()}@api.com`;
|
||||
const falseTestUserEmail = `organizations-event-types-false-user-${randomString()}@api.com`;
|
||||
let teammate1: User;
|
||||
let teammate2: User;
|
||||
let falseTestUser: User;
|
||||
@@ -98,23 +99,23 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-event-types-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
falseTestOrg = await organizationsRepositoryFixture.create({
|
||||
name: "False test org",
|
||||
name: `organizations-event-types-false-org-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: "Test org team",
|
||||
name: `organizations-event-types-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
falseTestTeam = await teamsRepositoryFixture.create({
|
||||
name: "Outside org team",
|
||||
name: `organizations-event-types-false-team-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: falseTestOrg.id } },
|
||||
});
|
||||
@@ -237,7 +238,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
successRedirectUrl: "https://masterchief.com/argentina/flan/video/1234",
|
||||
title: "Coding consultation collective",
|
||||
slug: "coding-consultation collective",
|
||||
slug: `organizations-event-types-collective-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -343,7 +344,7 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
it("should create a managed team event-type", async () => {
|
||||
const body: CreateTeamEventTypeInput_2024_06_14 = {
|
||||
title: "Coding consultation managed",
|
||||
slug: "coding-consultation-managed",
|
||||
slug: `organizations-event-types-managed-${randomString()}`,
|
||||
description: "Our team will review your codebase.",
|
||||
lengthInMinutes: 60,
|
||||
locations: [
|
||||
@@ -751,10 +752,10 @@ describe("Organizations Event Types Endpoints", () => {
|
||||
it("should return event type with default bookingFields if they are not defined", async () => {
|
||||
const eventTypeInput = {
|
||||
title: "unknown field event type two",
|
||||
slug: `organizations-event-types-unknown-${randomString()}`,
|
||||
description: "unknown field event type description two",
|
||||
length: 40,
|
||||
hidden: false,
|
||||
slug: "unknown-field-type-two",
|
||||
locations: [],
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
};
|
||||
|
||||
+7
-7
@@ -14,6 +14,7 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -33,10 +34,9 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
let membership2: Membership;
|
||||
let membershipCreatedViaApi: Membership;
|
||||
|
||||
const userEmail = "org-admin-membership-controller-e2e@api.com";
|
||||
const userEmail2 = "org-member-membership-controller-e2e@api.com";
|
||||
|
||||
const invitedUserEmail = "org-member-invited-membership-controller-e2e@api.com";
|
||||
const userEmail = `organizations-memberships-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-memberships-member-${randomString()}@api.com`;
|
||||
const invitedUserEmail = `organizations-memberships-invited-${randomString()}@api.com`;
|
||||
|
||||
let user: User;
|
||||
let user2: User;
|
||||
@@ -70,7 +70,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -217,7 +217,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
|
||||
const userEmail = "org-member-memberships-controller-e2e@api.com";
|
||||
const userEmail = `organizations-memberships-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -238,7 +238,7 @@ describe("Organizations Memberships Endpoints", () => {
|
||||
});
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Test Organization",
|
||||
name: `organizations-memberships-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
|
||||
+11
-7
@@ -12,6 +12,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
@@ -35,7 +36,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
|
||||
const userEmail = "mr-robot@schedules-api.com";
|
||||
const userEmail = `organizations-schedules-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let org: Team;
|
||||
let membership: Membership;
|
||||
@@ -53,7 +54,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
membershipFixtures = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Ecorp",
|
||||
name: `organizations-schedules-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -115,12 +116,15 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
let membershipFixtures: MembershipRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const userEmail = "mr-robot@schedules-api.com";
|
||||
const username = "mr-robot";
|
||||
const userEmail = `organizations-schedules-admin-${randomString()}@api.com`;
|
||||
const userEmail2 = `organizations-schedules-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
let user2: User;
|
||||
let org: Team;
|
||||
let profile: Profile;
|
||||
let membership: Membership;
|
||||
let membership2: Membership;
|
||||
let profile: Profile;
|
||||
let profile2: Profile;
|
||||
|
||||
let createdSchedule: ScheduleOutput_2024_06_11;
|
||||
|
||||
@@ -152,7 +156,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: "Ecorp",
|
||||
name: `organizations-schedules-admin-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
@@ -164,7 +168,7 @@ describe("Organizations Schedules Endpoints", () => {
|
||||
|
||||
profile = await profileRepositoryFixture.create({
|
||||
uid: `usr-${user.id}`,
|
||||
username: username,
|
||||
username: userEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: org.id,
|
||||
|
||||
+179
@@ -0,0 +1,179 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Transform, Type } from "class-transformer";
|
||||
import {
|
||||
ArrayMinSize,
|
||||
ArrayNotEmpty,
|
||||
IsArray,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsISO8601,
|
||||
IsNumber,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Max,
|
||||
Min,
|
||||
} from "class-validator";
|
||||
|
||||
enum Status {
|
||||
upcoming = "upcoming",
|
||||
recurring = "recurring",
|
||||
past = "past",
|
||||
cancelled = "cancelled",
|
||||
unconfirmed = "unconfirmed",
|
||||
}
|
||||
type StatusType = keyof typeof Status;
|
||||
|
||||
enum SortOrder {
|
||||
asc = "asc",
|
||||
desc = "desc",
|
||||
}
|
||||
type SortOrderType = keyof typeof SortOrder;
|
||||
|
||||
export class GetOrganizationsTeamsBookingsInput_2024_08_13 {
|
||||
// note(Lauris): filters
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === "string") {
|
||||
return value.split(",").map((status: string) => status.trim());
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@ArrayNotEmpty({ message: "status cannot be empty." })
|
||||
@IsEnum(Status, {
|
||||
each: true,
|
||||
message: "Invalid status. Allowed are upcoming, recurring, past, cancelled, unconfirmed",
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description:
|
||||
"Filter bookings by status. If you want to filter by multiple statuses, separate them with a comma.",
|
||||
example: "?status=upcoming,past",
|
||||
enum: Status,
|
||||
isArray: true,
|
||||
})
|
||||
status?: StatusType[];
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter bookings by the attendee's email address.",
|
||||
example: "example@domain.com",
|
||||
})
|
||||
attendeeEmail?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter bookings by the attendee's name.",
|
||||
example: "John Doe",
|
||||
})
|
||||
attendeeName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Transform(({ value }) => {
|
||||
if (typeof value === "string") {
|
||||
return value.split(",").map((eventTypeId: string) => parseInt(eventTypeId));
|
||||
}
|
||||
return value;
|
||||
})
|
||||
@IsArray()
|
||||
@IsNumber({}, { each: true })
|
||||
@ArrayMinSize(1, { message: "eventTypeIds must contain at least 1 event type id" })
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description:
|
||||
"Filter bookings by event type ids belonging to the team. Event type ids must be separated by a comma.",
|
||||
example: "?eventTypeIds=100,200",
|
||||
})
|
||||
eventTypeIds?: number[];
|
||||
|
||||
@IsInt()
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter bookings by event type id belonging to the team.",
|
||||
example: "?eventTypeId=100",
|
||||
})
|
||||
eventTypeId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: true }, { message: "fromDate must be a valid ISO 8601 date." })
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter bookings with start after this date string.",
|
||||
example: "?afterStart=2025-03-07T10:00:00.000Z",
|
||||
})
|
||||
afterStart?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsISO8601({ strict: true }, { message: "toDate must be a valid ISO 8601 date." })
|
||||
@ApiProperty({
|
||||
type: String,
|
||||
required: false,
|
||||
description: "Filter bookings with end before this date string.",
|
||||
example: "?beforeEnd=2025-03-07T11:00:00.000Z",
|
||||
})
|
||||
beforeEnd?: string;
|
||||
|
||||
// note(Lauris): sort
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder, {
|
||||
message: 'SortStart must be either "asc" or "desc".',
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: "Sort results by their start time in ascending or descending order.",
|
||||
example: "?sortStart=asc OR ?sortStart=desc",
|
||||
enum: SortOrder,
|
||||
})
|
||||
sortStart?: SortOrderType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder, {
|
||||
message: 'SortEnd must be either "asc" or "desc".',
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: "Sort results by their end time in ascending or descending order.",
|
||||
example: "?sortEnd=asc OR ?sortEnd=desc",
|
||||
enum: SortOrder,
|
||||
})
|
||||
sortEnd?: SortOrderType;
|
||||
|
||||
@IsOptional()
|
||||
@IsEnum(SortOrder, {
|
||||
message: 'SortCreated must be either "asc" or "desc".',
|
||||
})
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description:
|
||||
"Sort results by their creation time (when booking was made) in ascending or descending order.",
|
||||
example: "?sortCreated=asc OR ?sortCreated=desc",
|
||||
enum: SortOrder,
|
||||
})
|
||||
sortCreated?: SortOrderType;
|
||||
|
||||
// note(Lauris): pagination
|
||||
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(250)
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@IsOptional()
|
||||
skip?: number;
|
||||
}
|
||||
+364
@@ -0,0 +1,364 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
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 { OrganizationsTeamsBookingsModule } from "@/modules/organizations/controllers/teams/bookings/organizations-teams-bookings.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { User } from "@prisma/client";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_08_13,
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
} from "@calcom/platform-constants";
|
||||
import {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
|
||||
describe("Organizations TeamsBookings Endpoints 2024-08-13", () => {
|
||||
describe("Organization Team bookings", () => {
|
||||
let app: INestApplication;
|
||||
let organization: Team;
|
||||
let team1: Team;
|
||||
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let bookingsRepositoryFixture: BookingsRepositoryFixture;
|
||||
let schedulesService: SchedulesService_2024_04_15;
|
||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
|
||||
let oAuthClient: PlatformOAuthClient;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
let membershipsRepositoryFixture: MembershipRepositoryFixture;
|
||||
let hostsRepositoryFixture: HostsRepositoryFixture;
|
||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||
|
||||
const teamUserEmail = "orgUser1team1@api.com";
|
||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
||||
let teamUser: User;
|
||||
let teamUser2: User;
|
||||
|
||||
let team1EventTypeId: number;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
teamUserEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, OrganizationsTeamsBookingsModule],
|
||||
})
|
||||
)
|
||||
.overrideGuard(PermissionsGuard)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
bookingsRepositoryFixture = new BookingsRepositoryFixture(moduleRef);
|
||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||
|
||||
organization = await organizationsRepositoryFixture.create({ name: "organization team bookings" });
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
|
||||
team1 = await teamRepositoryFixture.create({
|
||||
name: "team 1",
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: organization.id } },
|
||||
createdByOAuthClient: {
|
||||
connect: {
|
||||
id: oAuthClient.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
teamUser = await userRepositoryFixture.create({
|
||||
email: teamUserEmail,
|
||||
locale: "it",
|
||||
name: "orgUser1team1",
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClient.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
teamUser2 = await userRepositoryFixture.create({
|
||||
email: teamUserEmail2,
|
||||
locale: "es",
|
||||
name: "orgUser2team1",
|
||||
platformOAuthClients: {
|
||||
connect: {
|
||||
id: oAuthClient.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||
name: "working time",
|
||||
timeZone: "Europe/Rome",
|
||||
isDefault: true,
|
||||
};
|
||||
await schedulesService.createUserSchedule(teamUser.id, userSchedule);
|
||||
await schedulesService.createUserSchedule(teamUser2.id, userSchedule);
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${teamUser.id}`,
|
||||
username: teamUserEmail,
|
||||
organization: {
|
||||
connect: {
|
||||
id: organization.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: teamUser.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await profileRepositoryFixture.create({
|
||||
uid: `usr-${teamUser2.id}`,
|
||||
username: teamUserEmail2,
|
||||
organization: {
|
||||
connect: {
|
||||
id: organization.id,
|
||||
},
|
||||
},
|
||||
user: {
|
||||
connect: {
|
||||
id: teamUser2.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: teamUser.id } },
|
||||
team: { connect: { id: team1.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: teamUser.id } },
|
||||
team: { connect: { id: organization.id } },
|
||||
accepted: true,
|
||||
});
|
||||
|
||||
const team1EventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||
schedulingType: "COLLECTIVE",
|
||||
team: {
|
||||
connect: { id: team1.id },
|
||||
},
|
||||
title: "Collective Event Type",
|
||||
slug: "collective-event-type",
|
||||
length: 60,
|
||||
assignAllTeamMembers: true,
|
||||
bookingFields: [],
|
||||
locations: [],
|
||||
});
|
||||
|
||||
team1EventTypeId = team1EventType.id;
|
||||
|
||||
await hostsRepositoryFixture.create({
|
||||
isFixed: true,
|
||||
user: {
|
||||
connect: {
|
||||
id: teamUser.id,
|
||||
},
|
||||
},
|
||||
eventType: {
|
||||
connect: {
|
||||
id: team1EventType.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
describe("create team bookings", () => {
|
||||
it("should create a team 1 booking", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||
eventTypeId: team1EventTypeId,
|
||||
attendee: {
|
||||
name: "alice",
|
||||
email: "alice@gmail.com",
|
||||
timeZone: "Europe/Madrid",
|
||||
language: "es",
|
||||
},
|
||||
meetingUrl: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201)
|
||||
.then(async (response) => {
|
||||
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
expect(responseDataIsBooking(responseBody.data)).toBe(true);
|
||||
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
expect(data.hosts.length).toEqual(1);
|
||||
expect(data.hosts[0].id).toEqual(teamUser.id);
|
||||
expect(data.status).toEqual("accepted");
|
||||
expect(data.start).toEqual(body.start);
|
||||
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString());
|
||||
expect(data.duration).toEqual(60);
|
||||
expect(data.eventTypeId).toEqual(team1EventTypeId);
|
||||
expect(data.attendees.length).toEqual(1);
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
});
|
||||
expect(data.meetingUrl).toEqual(body.meetingUrl);
|
||||
expect(data.absentHost).toEqual(false);
|
||||
} else {
|
||||
throw new Error(
|
||||
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("get team bookings", () => {
|
||||
it("should should get bookings by teamId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${organization.id}/teams/${team1.id}/bookings`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||
.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(team1EventTypeId);
|
||||
});
|
||||
});
|
||||
|
||||
it("should get bookings by teamId and eventTypeId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(
|
||||
`/v2/organizations/${organization.id}/teams/${team1.id}/bookings?eventTypeId=${team1EventTypeId}`
|
||||
)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.set(X_CAL_CLIENT_ID, oAuthClient.id)
|
||||
.set(X_CAL_SECRET_KEY, oAuthClient.secret)
|
||||
.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(team1EventTypeId);
|
||||
});
|
||||
});
|
||||
|
||||
it("should not get bookings by teamId and non existing eventTypeId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${organization.id}/teams/${team1.id}/bookings?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 not get bookings by non existing teamId", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.get(`/v2/organizations/${organization.id}/teams/90909/bookings`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
||||
async function createOAuthClient(organizationId: number) {
|
||||
const data = {
|
||||
logo: "logo-url",
|
||||
name: "name",
|
||||
redirectUris: ["http://localhost:5555"],
|
||||
permissions: 32,
|
||||
};
|
||||
const secret = "secret";
|
||||
|
||||
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
|
||||
return client;
|
||||
}
|
||||
|
||||
function responseDataIsBooking(data: any): data is BookingOutput_2024_08_13 {
|
||||
return !Array.isArray(data) && typeof data === "object" && data && "id" in data;
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
await oauthClientRepositoryFixture.delete(oAuthClient.id);
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
await userRepositoryFixture.deleteByEmail(teamUser.email);
|
||||
await userRepositoryFixture.deleteByEmail(teamUserEmail2);
|
||||
await bookingsRepositoryFixture.deleteAllBookings(teamUser.id, teamUser.email);
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user