Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9caaa0af32 | ||
|
|
f411c6ed11 | ||
|
|
245196fbf3 | ||
|
|
6be69b820c | ||
|
|
b63c9791ec | ||
|
|
8cc61a1fe9 | ||
|
|
be2e1274ff | ||
|
|
fa3b1c17ea | ||
|
|
015edd3c9b | ||
|
|
de05e8e989 | ||
|
|
78a6963cd9 | ||
|
|
ea827ebbc6 | ||
|
|
08e58851e7 | ||
|
|
da1b8137c2 | ||
|
|
d182e5c99a | ||
|
|
42259a113b | ||
|
+8 |
6561b92f40 | ||
|
|
9e5665f2fb | ||
|
|
07778d22ed | ||
|
|
56df8bdf7c | ||
|
|
a518c7152a | ||
|
|
c37a8a587b | ||
|
|
b7f7c33afc | ||
|
|
1d449ecc6b | ||
|
|
f582faed2b | ||
|
|
79a8ab79f8 | ||
|
|
ce87de4fc7 | ||
|
|
b128579729 | ||
|
|
3cc5d9c065 | ||
|
|
7897e14273 | ||
|
|
c581456586 | ||
|
|
36dd3e9e6c | ||
|
|
f137aee436 | ||
|
|
4f494d5a32 | ||
|
|
cce6e83578 | ||
|
|
c21ba636d2 | ||
|
|
5ccf5e6be8 | ||
|
|
66b3e735d8 | ||
|
|
7a8b9ededb | ||
|
|
501c49f369 | ||
|
|
536604455b | ||
|
|
cfb76a9689 | ||
|
|
30180edc0b | ||
|
|
2aa3d1d4e0 | ||
|
|
21694c039a | ||
|
|
a8c29b5459 | ||
|
|
6cd7454f43 | ||
|
|
99b2419dd7 | ||
|
|
f6d0a2ca09 | ||
|
|
f5ab9bd40b | ||
|
|
07085b9ffb | ||
|
|
14151b827f | ||
|
|
28b757f19c | ||
|
|
68455db860 | ||
|
|
96ff88d266 | ||
|
|
f88a91dad0 | ||
|
|
35059d7727 | ||
|
|
68f76e2acc | ||
|
|
80aac06ff3 | ||
|
|
0f47528ba2 | ||
|
|
113dc5fca6 | ||
|
|
0befdde5d7 | ||
|
|
b3ed6e39e3 | ||
|
|
e07b1eda32 | ||
|
|
8262894bde | ||
|
|
a3d7bcd22e | ||
|
|
62b753afba | ||
|
|
bb40b2f6bf | ||
|
|
f628dc83a8 | ||
|
|
2fd2578504 | ||
|
|
a29407349d | ||
|
|
e7df1ef493 | ||
|
|
b652ae7714 | ||
|
|
cec1c9ee46 | ||
|
|
3813fd70f7 | ||
|
|
dabd3b0fc3 | ||
|
|
95f9a02d29 | ||
|
|
f26d2998cb | ||
|
|
f558f8aca5 | ||
|
|
a6dd5185ea | ||
|
|
3da7694002 | ||
|
|
1ffb376789 | ||
|
|
8761b91f71 | ||
|
|
1dbef7f8f6 | ||
|
|
d4d5ac2b28 | ||
|
|
88c8ad8b5c | ||
|
|
0a08680084 | ||
|
|
65828545b9 | ||
|
|
3adb8e84fc | ||
|
|
2a29155641 | ||
|
|
4902c6a0f8 | ||
|
|
a22fd0c151 | ||
|
|
b6bd0eda43 | ||
|
|
25d052e8f0 | ||
|
|
f933432d2b | ||
|
|
ccf1d4430a | ||
|
|
2fd8d24204 | ||
|
|
b4c18f7bcc | ||
|
|
23c93d75ca | ||
|
|
91526d0bc7 | ||
|
|
a9f95937c4 | ||
|
|
74580157ba | ||
|
|
fcdbcceac0 | ||
|
|
aa7c924c2a | ||
|
|
3406ca72bc | ||
|
|
c29a2fc7cb | ||
|
|
0a4c7bcecc | ||
|
|
803dfa008c | ||
|
|
0eb6f1beb6 | ||
|
|
edd2e5e584 | ||
|
|
99a99b4b7e | ||
|
|
2ba6f4cb24 | ||
|
|
8a8c870513 | ||
|
|
eba335c903 | ||
|
|
9b2161cc19 | ||
|
|
b997c656df | ||
|
|
c9c5493de6 | ||
|
|
20a5775f31 | ||
|
|
b9b0a86b32 | ||
|
|
ad93066cf6 | ||
|
|
e6d76b630c | ||
|
|
b941b716d5 | ||
|
|
1ab9622416 | ||
|
|
15156c2225 | ||
|
|
cc202ed847 | ||
|
|
79d51a4989 | ||
|
|
53a846d10c | ||
|
|
1619f2daf2 | ||
|
|
57bd0b3df3 | ||
|
|
e359d13d9a | ||
|
|
3b7cb542ed | ||
|
|
fd7f6c32ae | ||
|
|
06d8b98b15 | ||
|
|
0c69c5dfea | ||
|
|
00f7d8be0d | ||
|
|
2e9beb8513 | ||
|
|
b6c6fa7365 | ||
|
|
c623785ec2 | ||
|
|
6a381cdc79 | ||
|
|
9d4496133e | ||
|
|
1452672495 | ||
|
|
9159c591ff | ||
|
|
408aee5851 | ||
|
|
29bbada77a | ||
|
|
6178092878 | ||
|
|
6f531d0be5 | ||
|
|
d7b723c914 | ||
|
|
aad3988d60 | ||
|
|
c8172ab790 | ||
|
|
657f9dfd41 | ||
|
|
c48b209c04 | ||
|
|
b9a460d8b4 | ||
|
|
a7727b5ca6 | ||
|
|
9b661b75f1 | ||
|
|
487cc0751d | ||
|
|
f7f36978e1 | ||
|
|
c1dbb7cffd | ||
|
|
e722e75a6f | ||
|
|
455572b5f4 | ||
|
|
2fe4462f62 | ||
|
|
f9dc788007 | ||
|
|
6e293d2985 | ||
|
|
00334c5610 | ||
|
|
2f9c8cb441 | ||
|
|
39173900b1 | ||
|
|
bd61e01a5d | ||
|
|
aa0b3a2778 | ||
|
|
170feeae5e | ||
|
|
a67b57b216 | ||
|
|
821a16648f | ||
|
|
727f7df2c2 | ||
|
|
be5f7f83b5 | ||
|
|
02a49e2b4b | ||
|
|
22f92f1ee3 | ||
|
|
c7a797c0e0 | ||
|
|
447436dfff | ||
|
|
c415b2d7f5 | ||
|
|
c10c87c98e | ||
|
|
85f96f492d | ||
|
|
b1350d0764 | ||
|
|
bac2d1f6f0 | ||
|
|
fec504496a | ||
|
|
890fc92148 | ||
|
|
557bd8bf25 | ||
|
|
d8027fe248 | ||
|
|
116c765c36 | ||
|
|
c6c3e228a3 | ||
|
|
7584645b8c | ||
|
|
32ff7ffed1 | ||
|
|
55113f20fe | ||
|
|
0a293308e7 | ||
|
|
e558daaa0b | ||
|
|
3f0a6718bc | ||
|
|
8ffebdba44 | ||
|
|
cc1101ac43 | ||
|
|
7a80a5f929 | ||
|
|
a27e1498b8 | ||
|
|
a697857fa0 | ||
|
|
e2f45bdf5a | ||
|
|
d2930bc243 | ||
|
|
b0b93f612e | ||
|
|
558c8be740 | ||
|
|
1aee1742d5 | ||
|
|
ab942ed4cd | ||
|
|
b5bec79354 | ||
|
|
b48d0e1f48 | ||
|
|
4a38b4dc89 | ||
|
|
ff12e387b3 | ||
|
|
6b5e144042 | ||
|
|
cd7b93a51e | ||
|
|
a76366cbcb | ||
|
|
860c04bbda | ||
|
+6 |
225313e391 | ||
|
|
3994929a51 | ||
|
|
ae38027d76 | ||
|
|
9036080fcf | ||
|
|
e6ef4e1988 | ||
|
|
3c2619a7a3 | ||
|
|
cfb09a1b21 | ||
|
|
5fe4e09975 | ||
|
|
542799e2cf | ||
|
|
3eda7858c0 | ||
|
|
b59bf7d1ee | ||
|
|
0dadeac352 | ||
|
|
5eb7a4d56e | ||
|
|
43fa944e2f | ||
|
|
1866b2845c | ||
|
|
2cfc63e6e3 | ||
|
|
4f66f3d2c6 | ||
|
|
ca3e7bd97d | ||
|
|
cafa3f9cf3 | ||
|
|
e04faffcbb | ||
|
|
6f1556a2fd | ||
|
|
d72a954896 | ||
|
|
abeb20373c | ||
|
|
422ef2f5b8 | ||
|
|
ec920eb2a8 | ||
|
|
795eecf300 | ||
|
|
8ae3d6d55b | ||
|
|
85070574da | ||
|
|
d1c2872f6e | ||
|
|
42d78f8099 | ||
|
|
f0d7a757d4 | ||
|
|
0b2f09ab7d | ||
|
|
355aaa7010 | ||
|
|
c27b73aa9a | ||
|
|
a622c7b624 | ||
|
|
19bd3e5849 | ||
|
|
7827028e0a | ||
|
|
ebc3ced687 | ||
|
|
b56824e626 | ||
|
|
460e511459 | ||
|
|
b4fd6f3442 | ||
|
|
0a7a0e3687 | ||
|
|
595fffbea0 | ||
|
|
218c9152da | ||
|
|
77fe23da49 | ||
|
|
a583db3fd4 | ||
|
|
cb1f803d0a | ||
|
|
479d6b5961 | ||
|
|
4ab3ec73c0 | ||
|
|
8dda418127 | ||
|
|
a7f24e777b | ||
|
|
f78fca594a | ||
|
|
e6a7ae7da3 | ||
|
|
ad5d92dff0 | ||
|
|
8bf46ab8b6 | ||
|
|
34b0753f8d | ||
|
|
0bd7227150 | ||
|
|
4d192ee328 | ||
|
|
63013d402e | ||
|
|
6b3ccbc61a | ||
|
|
2af5cba0d7 | ||
|
|
ae7bbaf414 | ||
|
|
4df4383e1e |
No files matched your search
+15
-20
@@ -107,6 +107,15 @@ NEXT_PUBLIC_POSTHOG_KEY=
|
|||||||
|
|
||||||
NEXT_PUBLIC_POSTHOG_HOST=
|
NEXT_PUBLIC_POSTHOG_HOST=
|
||||||
|
|
||||||
|
# plain.com config
|
||||||
|
|
||||||
|
PLAIN_API_KEY=
|
||||||
|
PLAIN_API_URL=https://api.plain.com/v1
|
||||||
|
PLAIN_HMAC_SECRET_KEY=
|
||||||
|
NEXT_PUBLIC_PLAIN_CHAT_ID=
|
||||||
|
PLAIN_CHAT_HMAC_SECRET_KEY=
|
||||||
|
NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS=
|
||||||
|
|
||||||
# Zendesk Config
|
# Zendesk Config
|
||||||
NEXT_PUBLIC_ZENDESK_KEY=
|
NEXT_PUBLIC_ZENDESK_KEY=
|
||||||
|
|
||||||
@@ -233,6 +242,7 @@ E2E_TEST_MAILHOG_ENABLED=
|
|||||||
|
|
||||||
# Cloudflare Turnstile
|
# Cloudflare Turnstile
|
||||||
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
||||||
|
NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=
|
||||||
CLOUDFLARE_TURNSTILE_SECRET=
|
CLOUDFLARE_TURNSTILE_SECRET=
|
||||||
|
|
||||||
# Set the following value to true if you wish to enable Team Impersonation
|
# Set the following value to true if you wish to enable Team Impersonation
|
||||||
@@ -346,29 +356,9 @@ E2E_TEST_OIDC_USER_PASSWORD=
|
|||||||
# provide a value between 0 and 100 to ensure the percentage of traffic
|
# provide a value between 0 and 100 to ensure the percentage of traffic
|
||||||
# redirected from the legacy to the future pages
|
# redirected from the legacy to the future pages
|
||||||
AB_TEST_BUCKET_PROBABILITY=50
|
AB_TEST_BUCKET_PROBABILITY=50
|
||||||
# whether we redirect to the future/event-types from event-types or not
|
|
||||||
APP_ROUTER_EVENT_TYPES_ENABLED=0
|
|
||||||
APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0
|
|
||||||
APP_ROUTER_APPS_SLUG_ENABLED=0
|
|
||||||
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
|
||||||
# whether we redirect to the future/apps/categories from /apps/categories or not
|
|
||||||
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
|
|
||||||
# whether we redirect to the future/apps/categories/[category] from /apps/categories/[category] or not
|
|
||||||
APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED=0
|
|
||||||
APP_ROUTER_BOOKING_ENABLED=0
|
|
||||||
APP_ROUTER_BOOKINGS_STATUS_ENABLED=0
|
|
||||||
APP_ROUTER_GETTING_STARTED_STEP_ENABLED=0
|
|
||||||
APP_ROUTER_APPS_ENABLED=0
|
APP_ROUTER_APPS_ENABLED=0
|
||||||
APP_ROUTER_TEAM_ENABLED=0
|
APP_ROUTER_TEAM_ENABLED=0
|
||||||
APP_ROUTER_TEAMS_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_LOGIN_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_LOGOUT_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_NEW_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_SAML_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_ERROR_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_PLATFORM_ENABLED=0
|
|
||||||
APP_ROUTER_AUTH_OAUTH2_ENABLED=0
|
|
||||||
|
|
||||||
# disable setry server source maps
|
# disable setry server source maps
|
||||||
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
||||||
@@ -411,3 +401,8 @@ REPLEXICA_API_KEY=
|
|||||||
|
|
||||||
# Comma-separated list of DSyncData.directoryId to log SCIM API requests for. It can be enabled temporarily for debugging the requests being sent to SCIM server.
|
# Comma-separated list of DSyncData.directoryId to log SCIM API requests for. It can be enabled temporarily for debugging the requests being sent to SCIM server.
|
||||||
DIRECTORY_IDS_TO_LOG=
|
DIRECTORY_IDS_TO_LOG=
|
||||||
|
|
||||||
|
|
||||||
|
# Set this when Cal.com is used to serve only one organization's booking pages
|
||||||
|
# Read more about it in the README.md
|
||||||
|
NEXT_PUBLIC_SINGLE_ORG_SLUG=
|
||||||
@@ -1,3 +1,6 @@
|
|||||||
|
/apps/api/**/* @calcom/Platform
|
||||||
|
/deploy/**/* @calcom/Foundation
|
||||||
|
/infra/**/* @calcom/Foundation
|
||||||
/packages/app-store/applecalendar/**/* @calcom/Foundation
|
/packages/app-store/applecalendar/**/* @calcom/Foundation
|
||||||
/packages/app-store/caldavcalendar/**/* @calcom/Foundation
|
/packages/app-store/caldavcalendar/**/* @calcom/Foundation
|
||||||
/packages/app-store/exchange2013calendar/**/* @calcom/Foundation
|
/packages/app-store/exchange2013calendar/**/* @calcom/Foundation
|
||||||
@@ -9,3 +12,12 @@
|
|||||||
/packages/app-store/larkcalendar/**/* @calcom/Foundation
|
/packages/app-store/larkcalendar/**/* @calcom/Foundation
|
||||||
/packages/app-store/office365calendar/**/* @calcom/Foundation
|
/packages/app-store/office365calendar/**/* @calcom/Foundation
|
||||||
/packages/app-store/zohocalendar/**/* @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."
|
# "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
|
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||||
schedule:
|
schedule:
|
||||||
# Runs “every 5 minutes” (see https://crontab.guru)
|
# Runs “every minute” (see https://crontab.guru)
|
||||||
- cron: "*/5 * * * *"
|
- cron: "* * * * *"
|
||||||
jobs:
|
jobs:
|
||||||
cron-webhookTriggers:
|
cron-webhookTriggers:
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: replexica/replexica@main
|
- uses: lingodotdev/lingo.dev@main
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -12,16 +12,6 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- name: Run Linting with Reports
|
- name: Run Lint
|
||||||
run: yarn lint:report
|
run: yarn lint
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Merge lint reports
|
|
||||||
run: jq -s '[.[]]|flatten' lint-results/*.json &> lint-results/eslint_report.json
|
|
||||||
|
|
||||||
- name: Upload ESLint report
|
|
||||||
if: ${{ always() }}
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: lint-results
|
|
||||||
path: lint-results
|
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
has-files-requiring-all-checks:
|
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
|
- name: Get Latest Commit SHA
|
||||||
id: get_sha
|
id: get_sha
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ on: # yamllint disable-line rule:truthy
|
|||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
|
- published
|
||||||
# in case manual trigger is needed
|
# in case manual trigger is needed
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/index.cjs b/index.cjs
|
diff --git a/index.cjs b/index.cjs
|
||||||
index c83f700ae9998cd87b4c2d66ecbb2ad3d7b4603c..76a2200b57f0b9243e2c61464d578b67746ad5a4 100644
|
index c83f700..da6fc7e 100644
|
||||||
--- a/index.cjs
|
--- a/index.cjs
|
||||||
+++ b/index.cjs
|
+++ b/index.cjs
|
||||||
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
|
@@ -13,8 +13,8 @@ function withMetadataArgument(func, _arguments) {
|
||||||
Vendored
Whitespace-only changes.
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
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
Whitespace-only changes.
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+6
@@ -0,0 +1,6 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+7
@@ -0,0 +1,7 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+8
@@ -0,0 +1,8 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/app-store-cli"
|
||||||
|
- "@calcom/platform-constants"
|
||||||
|
- "@calcom/platform-enums"
|
||||||
|
- "@calcom/platform-types"
|
||||||
|
- "@calcom/platform-utils"
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
Whitespace-only changes.
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
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
Whitespace-only changes.
@@ -485,6 +485,15 @@ Don't code but still want to contribute? Join our [Discussions](https://github.c
|
|||||||
|
|
||||||
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
|
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
|
||||||
|
|
||||||
|
## Single Org Mode
|
||||||
|
If you want to have booker.yourcompany.com to be the domain used for both dashboard(e.g. https://booker.yourcompany.com/event-types) and booking pages(e.g. https://booker.yourcompany.com/john.joe/15min).
|
||||||
|
- Set the `NEXT_PUBLIC_SINGLE_ORG_SLUG` environment variable to the slug of the organization you want to use. `NEXT_PUBLIC_SINGLE_ORG_SLUG=booker`
|
||||||
|
- Set the `NEXT_PUBLIC_WEBAPP_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBAPP_URL=https://booker.yourcompany.com`.
|
||||||
|
- Set the `NEXT_PUBLIC_WEBSITE_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXT_PUBLIC_WEBSITE_URL=https://booker.yourcompany.com`.
|
||||||
|
- Set the `NEXTAUTH_URL` environment variable to the URL of the Cal.com self-hosted instance e.g. `NEXTAUTH_URL=https://booker.yourcompany.com`.
|
||||||
|
|
||||||
|
Note: It causes root to serve the dashboard and not the organization profile page which shows all bookable users in the organization.
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
### Obtaining the Google API Credentials
|
### Obtaining the Google API Credentials
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ test.describe("Org", () => {
|
|||||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
test("Org User(Rick) Page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/peer/embed");
|
const response = await page.goto("https://i.cal.com/team-rick/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
|
await expect(page.locator("text=Used by Checkly")).toBeVisible();
|
||||||
await expectPageToBeRenderedWithEmbedSsr(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
test("Org User Event(/team-rick/test-event) Page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/peer/meet/embed");
|
const response = await page.goto("https://i.cal.com/team-rick/test-event/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||||
@@ -58,10 +58,10 @@ test.describe("Org", () => {
|
|||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Group Meeting");
|
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(
|
||||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
"Join us for a meeting with multiple people"
|
||||||
// 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"] [data-testid="avatar"]').all()).length).toBe(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -75,7 +75,9 @@ test.describe("Org", () => {
|
|||||||
|
|
||||||
test.describe("Browse the Engineering Team", async () => {
|
test.describe("Browse the Engineering Team", async () => {
|
||||||
test("By User Navigation", async ({ page }) => {
|
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 page.click('text="Engineering"');
|
||||||
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
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 { Request, Response } from "express";
|
||||||
import type { NextApiResponse, NextApiRequest } from "next";
|
import type { NextApiResponse, NextApiRequest } from "next";
|
||||||
import { createMocks } from "node-mocks-http";
|
import { createMocks } from "node-mocks-http";
|
||||||
import { describe, it, expect, vi } from "vitest";
|
import { describe, it, expect, vi } from "vitest";
|
||||||
|
|
||||||
|
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
|
||||||
import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";
|
import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";
|
||||||
|
|
||||||
type CustomNextApiRequest = NextApiRequest & Request;
|
type CustomNextApiRequest = NextApiRequest & Request;
|
||||||
type CustomNextApiResponse = NextApiResponse & Response;
|
type CustomNextApiResponse = NextApiResponse & Response;
|
||||||
|
|
||||||
|
const testUserId = 123;
|
||||||
|
|
||||||
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
|
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
|
||||||
checkRateLimitAndThrowError: vi.fn(),
|
checkRateLimitAndThrowError: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/autoLock", () => ({
|
||||||
|
handleAutoLock: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe("rateLimitApiKey middleware", () => {
|
describe("rateLimitApiKey middleware", () => {
|
||||||
it("should return 401 if no apiKey is provided", async () => {
|
it("should return 401 if no apiKey is provided", async () => {
|
||||||
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: {},
|
query: {},
|
||||||
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
@@ -31,6 +41,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
const { req, res } = createMocks({
|
const { req, res } = createMocks({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: { apiKey: "test-key" },
|
query: { apiKey: "test-key" },
|
||||||
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
||||||
@@ -43,7 +54,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||||
identifier: "test-key",
|
identifier: testUserId.toString(),
|
||||||
rateLimitingType: "api",
|
rateLimitingType: "api",
|
||||||
onRateLimiterResponse: expect.any(Function),
|
onRateLimiterResponse: expect.any(Function),
|
||||||
});
|
});
|
||||||
@@ -53,17 +64,23 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
const { req, res } = createMocks({
|
const { req, res } = createMocks({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: { apiKey: "test-key" },
|
query: { apiKey: "test-key" },
|
||||||
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rateLimiterResponse = {
|
const rateLimiterResponse: RatelimitResponse = {
|
||||||
limit: 100,
|
limit: 100,
|
||||||
remaining: 99,
|
remaining: 99,
|
||||||
reset: Date.now(),
|
reset: Date.now(),
|
||||||
|
success: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
onRateLimiterResponse(rateLimiterResponse);
|
// @ts-ignore
|
||||||
});
|
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||||
|
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||||
|
onRateLimiterResponse(rateLimiterResponse);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
@@ -77,6 +94,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
const { req, res } = createMocks({
|
const { req, res } = createMocks({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
query: { apiKey: "test-key" },
|
query: { apiKey: "test-key" },
|
||||||
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
||||||
@@ -87,4 +105,139 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
expect(res._getStatusCode()).toBe(429);
|
expect(res._getStatusCode()).toBe(429);
|
||||||
expect(res._getJSONData()).toEqual({ message: "Rate limit exceeded" });
|
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 type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
|
import { handleAutoLock } from "@calcom/lib/autoLock";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
|
||||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
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" });
|
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||||
|
|
||||||
// TODO: Add a way to add trusted api keys
|
// TODO: Add a way to add trusted api keys
|
||||||
try {
|
try {
|
||||||
|
const identifier = req.userId.toString();
|
||||||
await checkRateLimitAndThrowError({
|
await checkRateLimitAndThrowError({
|
||||||
identifier: req.query.apiKey as string,
|
identifier,
|
||||||
rateLimitingType: "api",
|
rateLimitingType: "api",
|
||||||
onRateLimiterResponse: (response) => {
|
onRateLimiterResponse: async (response) => {
|
||||||
res.setHeader("X-RateLimit-Limit", response.limit);
|
res.setHeader("X-RateLimit-Limit", response.limit);
|
||||||
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||||
res.setHeader("X-RateLimit-Reset", response.reset);
|
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) {
|
} 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();
|
await next();
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { checkIfEmailInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller";
|
import { checkIfEmailIsBlockedInWatchlistController } from "@calcom/features/watchlist/operations/check-if-email-in-watchlist.controller";
|
||||||
|
|
||||||
export async function isLockedOrBlocked(req: NextApiRequest) {
|
export async function isLockedOrBlocked(req: NextApiRequest) {
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
if (!user?.email) return false;
|
if (!user?.email) return false;
|
||||||
return user.locked || (await checkIfEmailInWatchlistController(user.email));
|
return user.locked || (await checkIfEmailIsBlockedInWatchlistController(user.email));
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,23 @@ export const schemaBookingGetParams = z.object({
|
|||||||
|
|
||||||
export type Status = z.infer<typeof schemaBookingGetParams>["status"];
|
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
|
const schemaBookingEditParams = z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -9,11 +9,25 @@ export const schemaSelectedCalendarBaseBodyParams = SelectedCalendar;
|
|||||||
|
|
||||||
export const schemaSelectedCalendarPublic = SelectedCalendar.omit({});
|
export const schemaSelectedCalendarPublic = SelectedCalendar.omit({});
|
||||||
|
|
||||||
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams.partial({
|
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||||
userId: true,
|
.partial({
|
||||||
});
|
userId: true,
|
||||||
|
})
|
||||||
|
.omit({
|
||||||
|
// id will be set by the database
|
||||||
|
id: true,
|
||||||
|
// No eventTypeId support in API v1
|
||||||
|
eventTypeId: true,
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams.partial();
|
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||||
|
.omit({
|
||||||
|
// id is decided by DB
|
||||||
|
id: true,
|
||||||
|
// No eventTypeId support in API v1
|
||||||
|
eventTypeId: true,
|
||||||
|
})
|
||||||
|
.partial();
|
||||||
|
|
||||||
export const selectedCalendarIdSchema = schemaQueryIdAsString.transform((v, ctx) => {
|
export const selectedCalendarIdSchema = schemaQueryIdAsString.transform((v, ctx) => {
|
||||||
/** We can assume the first part is the userId since it's an integer */
|
/** We can assume the first part is the userId since it's an integer */
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// Helper schema for JSON fields
|
// Helper schema for JSON fields
|
||||||
type Literal = boolean | number | string;
|
type Literal = boolean | number | string | null;
|
||||||
type Json = Literal | { [key: string]: Json } | Json[];
|
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(() =>
|
export const jsonSchema: z.ZodSchema<Json> = z.lazy(() =>
|
||||||
z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
|
z.union([literalSchema, jsonObjectSchema, jsonArraySchema])
|
||||||
);
|
);
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
"lint:fix": "eslint . --ext .ts,.js,.tsx,.jsx --fix",
|
||||||
"start": "PORT=3003 next start",
|
"start": "PORT=3003 next start",
|
||||||
"docker-start-api": "PORT=80 next start",
|
"docker-start-api": "PORT=80 next start",
|
||||||
"type-check": "tsc --pretty --noEmit"
|
"type-check": "tsc --pretty --noEmit",
|
||||||
|
"type-check:ci": "tsc-absolute --pretty --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@calcom/tsconfig": "*",
|
"@calcom/tsconfig": "*",
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import type { NextApiRequest } from "next";
|
|||||||
|
|
||||||
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
import handleCancelBooking from "@calcom/features/bookings/lib/handleCancelBooking";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
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";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,6 +70,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
...req.query,
|
...req.query,
|
||||||
allRemainingBookings: req.query.allRemainingBookings === "true",
|
allRemainingBookings: req.query.allRemainingBookings === "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Normalizing for universal handler
|
// Normalizing for universal handler
|
||||||
req.body = { id, allRemainingBookings, cancellationReason };
|
req.body = { id, allRemainingBookings, cancellationReason };
|
||||||
return await handleCancelBooking(req);
|
return await handleCancelBooking(req);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
|||||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
|
|
||||||
@@ -213,6 +214,10 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
|||||||
*/
|
*/
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||||
|
req.body = {
|
||||||
|
...req.body,
|
||||||
|
creationSource: CreationSource.API_V1,
|
||||||
|
};
|
||||||
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||||
|
|
||||||
if (isOrganizationOwnerOrAdmin) {
|
if (isOrganizationOwnerOrAdmin) {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import type { UserWithCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
|
import type { UserWithCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
|
||||||
import { getConnectedDestinationCalendars } from "@calcom/lib/getConnectedDestinationCalendars";
|
import { getConnectedDestinationCalendarsAndEnsureDefaultsInDb } from "@calcom/lib/getConnectedDestinationCalendars";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { extractUserIdsFromQuery } from "~/lib/utils/extractUserIdsFromQuery";
|
import { extractUserIdsFromQuery } from "~/lib/utils/extractUserIdsFromQuery";
|
||||||
@@ -104,12 +105,8 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
|
|
||||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||||
|
|
||||||
const usersWithCalendars = await prisma.user.findMany({
|
const usersWithCalendars = await UserRepository.findManyByIdsIncludeDestinationAndSelectedCalendars({
|
||||||
where: { id: { in: userIds } },
|
ids: userIds,
|
||||||
include: {
|
|
||||||
selectedCalendars: true,
|
|
||||||
destinationCalendar: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return await getConnectedCalendars(usersWithCalendars);
|
return await getConnectedCalendars(usersWithCalendars);
|
||||||
@@ -117,11 +114,12 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
|
|
||||||
async function getConnectedCalendars(users: UserWithCalendars[]) {
|
async function getConnectedCalendars(users: UserWithCalendars[]) {
|
||||||
const connectedDestinationCalendarsPromises = users.map((user) =>
|
const connectedDestinationCalendarsPromises = users.map((user) =>
|
||||||
getConnectedDestinationCalendars(user, false, prisma).then((connectedCalendarsResult) =>
|
getConnectedDestinationCalendarsAndEnsureDefaultsInDb({ user, onboarding: false, prisma }).then(
|
||||||
connectedCalendarsResult.connectedCalendars.map((calendar) => ({
|
(connectedCalendarsResult) =>
|
||||||
userId: user.id,
|
connectedCalendarsResult.connectedCalendars.map((calendar) => ({
|
||||||
...calendar,
|
userId: user.id,
|
||||||
}))
|
...calendar,
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const connectedDestinationCalendars = await Promise.all(connectedDestinationCalendarsPromises);
|
const connectedDestinationCalendars = await Promise.all(connectedDestinationCalendarsPromises);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
import { createContext } from "@calcom/trpc/server/createContext";
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
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,
|
language: data.language,
|
||||||
teamId: data.teamId,
|
teamId: data.teamId,
|
||||||
usernameOrEmail: data.usernameOrEmail,
|
usernameOrEmail: data.usernameOrEmail,
|
||||||
|
creationSource: CreationSource.API_V1,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
|
|
||||||
import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
||||||
|
|
||||||
@@ -49,7 +49,9 @@ import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
|||||||
export async function deleteHandler(req: NextApiRequest) {
|
export async function deleteHandler(req: NextApiRequest) {
|
||||||
const { query } = req;
|
const { query } = req;
|
||||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||||
await prisma.selectedCalendar.delete({ where: { userId_integration_externalId } });
|
await SelectedCalendarRepository.deleteUserLevel({
|
||||||
|
where: userId_integration_externalId,
|
||||||
|
});
|
||||||
return { message: `Selected Calendar with id: ${query.id} deleted successfully` };
|
return { message: `Selected Calendar with id: ${query.id} deleted successfully` };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
|
|
||||||
import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
|
||||||
|
|
||||||
@@ -49,8 +49,8 @@ import { schemaSelectedCalendarPublic, selectedCalendarIdSchema } from "~/lib/va
|
|||||||
export async function getHandler(req: NextApiRequest) {
|
export async function getHandler(req: NextApiRequest) {
|
||||||
const { query } = req;
|
const { query } = req;
|
||||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||||
const data = await prisma.selectedCalendar.findUniqueOrThrow({
|
const data = await SelectedCalendarRepository.findUserLevelUniqueOrThrow({
|
||||||
where: { userId_integration_externalId },
|
where: userId_integration_externalId,
|
||||||
});
|
});
|
||||||
return { selected_calendar: schemaSelectedCalendarPublic.parse(data) };
|
return { selected_calendar: schemaSelectedCalendarPublic.parse(data) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { NextApiRequest } from "next";
|
|||||||
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import type { UpdateArguments } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -56,7 +58,7 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
const { query, isSystemWideAdmin } = req;
|
const { query, isSystemWideAdmin } = req;
|
||||||
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
|
||||||
const { userId: bodyUserId, ...data } = schemaSelectedCalendarUpdateBodyParams.parse(req.body);
|
const { userId: bodyUserId, ...data } = schemaSelectedCalendarUpdateBodyParams.parse(req.body);
|
||||||
const args: Prisma.SelectedCalendarUpdateArgs = { where: { userId_integration_externalId }, data };
|
const args: UpdateArguments = { where: { ...userId_integration_externalId }, data };
|
||||||
|
|
||||||
if (!isSystemWideAdmin && bodyUserId)
|
if (!isSystemWideAdmin && bodyUserId)
|
||||||
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
||||||
@@ -67,7 +69,7 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await prisma.selectedCalendar.update(args);
|
const result = await SelectedCalendarRepository.updateUserLevel(args);
|
||||||
return { selected_calendar: schemaSelectedCalendarPublic.parse(result) };
|
return { selected_calendar: schemaSelectedCalendarPublic.parse(result) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import type { FindManyArgs } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
|
|
||||||
import { schemaSelectedCalendarPublic } from "~/lib/validations/selected-calendar";
|
import { schemaSelectedCalendarPublic } from "~/lib/validations/selected-calendar";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
@@ -34,7 +34,7 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
|||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, isSystemWideAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
/* Admin gets all selected calendar by default, otherwise only the user's ones */
|
/* Admin gets all selected calendar by default, otherwise only the user's ones */
|
||||||
const args: Prisma.SelectedCalendarFindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
|
const args: FindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
|
||||||
|
|
||||||
/** Only admins can query other users */
|
/** Only admins can query other users */
|
||||||
if (!isSystemWideAdmin && req.query.userId)
|
if (!isSystemWideAdmin && req.query.userId)
|
||||||
@@ -46,7 +46,7 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
if (Array.isArray(query.userId)) args.orderBy = { userId: "asc" };
|
if (Array.isArray(query.userId)) args.orderBy = { userId: "asc" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await prisma.selectedCalendar.findMany(args);
|
const data = await SelectedCalendarRepository.findManyUserLevel(args);
|
||||||
return { selected_calendars: data.map((v) => schemaSelectedCalendarPublic.parse(v)) };
|
return { selected_calendars: data.map((v) => schemaSelectedCalendarPublic.parse(v)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { NextApiRequest } from "next";
|
|||||||
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import { SelectedCalendarRepository } from "@calcom/lib/server/repository/selectedCalendar";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -52,7 +53,11 @@ import {
|
|||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isSystemWideAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { userId: bodyUserId, ...body } = schemaSelectedCalendarBodyParams.parse(req.body);
|
const { userId: bodyUserId, ...body } = schemaSelectedCalendarBodyParams.parse(req.body);
|
||||||
const args: Prisma.SelectedCalendarCreateArgs = { data: { ...body, userId } };
|
const args: {
|
||||||
|
data: Prisma.SelectedCalendarUncheckedCreateInput;
|
||||||
|
} = {
|
||||||
|
data: { ...body, userId },
|
||||||
|
};
|
||||||
|
|
||||||
if (!isSystemWideAdmin && bodyUserId)
|
if (!isSystemWideAdmin && bodyUserId)
|
||||||
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
||||||
@@ -63,7 +68,7 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await prisma.selectedCalendar.create(args);
|
const data = await SelectedCalendarRepository.create(args.data);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selected_calendar: schemaSelectedCalendarPublic.parse(data),
|
selected_calendar: schemaSelectedCalendarPublic.parse(data),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
|
|||||||
|
|
||||||
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
|
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
|
||||||
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||||
|
import { getDubCustomer } from "@calcom/features/auth/lib/dub";
|
||||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
@@ -192,11 +193,26 @@ const generateTeamCheckoutSession = async ({
|
|||||||
pendingPaymentTeamId: number;
|
pendingPaymentTeamId: number;
|
||||||
ownerId: number;
|
ownerId: number;
|
||||||
}) => {
|
}) => {
|
||||||
const customer = await getStripeCustomerIdFromUserId(ownerId);
|
const [customer, dubCustomer] = await Promise.all([
|
||||||
|
getStripeCustomerIdFromUserId(ownerId),
|
||||||
|
getDubCustomer(ownerId.toString()),
|
||||||
|
]);
|
||||||
|
|
||||||
const session = await stripe.checkout.sessions.create({
|
const session = await stripe.checkout.sessions.create({
|
||||||
customer,
|
customer,
|
||||||
mode: "subscription",
|
mode: "subscription",
|
||||||
allow_promotion_codes: true,
|
...(dubCustomer?.discount?.couponId
|
||||||
|
? {
|
||||||
|
discounts: [
|
||||||
|
{
|
||||||
|
coupon:
|
||||||
|
process.env.NODE_ENV !== "production" && dubCustomer.discount.couponTestId
|
||||||
|
? dubCustomer.discount.couponTestId
|
||||||
|
: dubCustomer.discount.couponId,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: { allow_promotion_codes: true }),
|
||||||
success_url: `${WEBAPP_URL}/api/teams/api/create?session_id={CHECKOUT_SESSION_ID}`,
|
success_url: `${WEBAPP_URL}/api/teams/api/create?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
|
cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
|
||||||
line_items: [
|
line_items: [
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import type { NextApiRequest } from "next";
|
|||||||
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
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";
|
import { schemaUserCreateBodyParams } from "~/lib/validations/user";
|
||||||
|
|
||||||
@@ -92,7 +93,9 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
// If user is not ADMIN, return unauthorized.
|
// If user is not ADMIN, return unauthorized.
|
||||||
if (!isSystemWideAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" });
|
if (!isSystemWideAdmin) throw new HttpError({ statusCode: 401, message: "You are not authorized" });
|
||||||
const data = await schemaUserCreateBodyParams.parseAsync(req.body);
|
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;
|
req.statusCode = 201;
|
||||||
return { user };
|
return { user };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ describe("POST /api/attendees", () => {
|
|||||||
bookingId: 1,
|
bookingId: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
||||||
req.isSystemWideAdmin = true;
|
req.isSystemWideAdmin = true;
|
||||||
req.userId = 123;
|
req.userId = 123;
|
||||||
@@ -84,7 +86,8 @@ describe("POST /api/attendees", () => {
|
|||||||
timeZone: "UTC",
|
timeZone: "UTC",
|
||||||
bookingId: 1,
|
bookingId: 1,
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
prismaMock.attendee.create.mockResolvedValue(attendeeData);
|
||||||
|
|
||||||
req.userId = 123;
|
req.userId = 123;
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
|
|||||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||||
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
|
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { Booking } from "@calcom/prisma";
|
import type { Booking } from "@calcom/prisma/client";
|
||||||
|
import { CreationSource } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import handler from "../../../pages/api/bookings/_post";
|
import handler from "../../../pages/api/bookings/_post";
|
||||||
|
|
||||||
@@ -220,6 +221,34 @@ describe.skipIf(true)("POST /api/bookings", () => {
|
|||||||
});
|
});
|
||||||
expect(previousBooking?.status).toBe("cancelled");
|
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", () => {
|
describe("Recurring event-type", () => {
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ describe("DELETE /api/event-types/[id]", () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetAllMocks();
|
vi.resetAllMocks();
|
||||||
// Mocking membership.findFirst
|
// Mocking membership.findFirst
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
prismaMock.membership.findFirst.mockImplementation(({ where }) => {
|
prismaMock.membership.findFirst.mockImplementation(({ where }) => {
|
||||||
const { userId, teamId, accepted, role } = where;
|
const { userId, teamId, accepted, role } = where;
|
||||||
const mockData = [
|
const mockData = [
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ describe("POST /api/event-types", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
req.userId = memberUserId;
|
req.userId = memberUserId;
|
||||||
|
// @ts-expect-error - Return type is wrong
|
||||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
@@ -114,6 +115,7 @@ describe("POST /api/event-types", () => {
|
|||||||
req.isSystemWideAdmin = true;
|
req.isSystemWideAdmin = true;
|
||||||
req.userId = adminUserId;
|
req.userId = adminUserId;
|
||||||
|
|
||||||
|
// @ts-expect-error - Return type is wrong
|
||||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
@@ -138,6 +140,7 @@ describe("POST /api/event-types", () => {
|
|||||||
req.isSystemWideAdmin = true;
|
req.isSystemWideAdmin = true;
|
||||||
req.userId = adminUserId;
|
req.userId = adminUserId;
|
||||||
|
|
||||||
|
// @ts-expect-error - Return type is wrong
|
||||||
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => true);
|
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => true);
|
||||||
vi.mocked(checkParentEventOwnership).mockImplementationOnce(async () => undefined);
|
vi.mocked(checkParentEventOwnership).mockImplementationOnce(async () => undefined);
|
||||||
vi.mocked(checkUserMembership).mockImplementationOnce(async () => undefined);
|
vi.mocked(checkUserMembership).mockImplementationOnce(async () => undefined);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import type { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import { createMocks } from "node-mocks-http";
|
import { createMocks } from "node-mocks-http";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import type { ILicenseKeyService } from "@calcom/ee/common/server/LicenseKeyService";
|
||||||
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
|
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
@@ -21,7 +22,7 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("Verify API key", () => {
|
describe("Verify API key", () => {
|
||||||
let service: LicenseKeyService;
|
let service: ILicenseKeyService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
service = await LicenseKeyService.create();
|
service = await LicenseKeyService.create();
|
||||||
|
|||||||
@@ -79,6 +79,15 @@ describe("POST /api/selected-calendars", () => {
|
|||||||
integration: "google",
|
integration: "google",
|
||||||
externalId: "ext123",
|
externalId: "ext123",
|
||||||
userId: 444444,
|
userId: 444444,
|
||||||
|
id: "xxx-xxx",
|
||||||
|
eventTypeId: null,
|
||||||
|
domainWideDelegationCredentialId: null,
|
||||||
|
googleChannelId: null,
|
||||||
|
googleChannelKind: null,
|
||||||
|
googleChannelResourceId: null,
|
||||||
|
googleChannelResourceUri: null,
|
||||||
|
googleChannelExpiration: null,
|
||||||
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
@@ -104,10 +113,19 @@ describe("POST /api/selected-calendars", () => {
|
|||||||
req.userId = 333333;
|
req.userId = 333333;
|
||||||
|
|
||||||
prismaMock.selectedCalendar.create.mockResolvedValue({
|
prismaMock.selectedCalendar.create.mockResolvedValue({
|
||||||
|
id: "xxx-xxx",
|
||||||
credentialId: 1,
|
credentialId: 1,
|
||||||
integration: "google",
|
integration: "google",
|
||||||
externalId: "ext123",
|
externalId: "ext123",
|
||||||
userId: 333333,
|
userId: 333333,
|
||||||
|
googleChannelId: null,
|
||||||
|
googleChannelKind: null,
|
||||||
|
googleChannelResourceId: null,
|
||||||
|
googleChannelResourceUri: null,
|
||||||
|
googleChannelExpiration: null,
|
||||||
|
domainWideDelegationCredentialId: null,
|
||||||
|
eventTypeId: null,
|
||||||
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await handler(req, res);
|
await handler(req, res);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,8 @@ import prismock from "../../../../../../tests/libs/__mocks__/prisma";
|
|||||||
|
|
||||||
import { describe, expect, it, beforeEach } from "vitest";
|
import { describe, expect, it, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
import { WatchlistSeverity } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { isLockedOrBlocked } from "../../../lib/utils/isLockedOrBlocked";
|
import { isLockedOrBlocked } from "../../../lib/utils/isLockedOrBlocked";
|
||||||
|
|
||||||
describe("isLockedOrBlocked", () => {
|
describe("isLockedOrBlocked", () => {
|
||||||
@@ -16,6 +18,7 @@ describe("isLockedOrBlocked", () => {
|
|||||||
{
|
{
|
||||||
type: "DOMAIN",
|
type: "DOMAIN",
|
||||||
value: "blocked.com",
|
value: "blocked.com",
|
||||||
|
severity: WatchlistSeverity.CRITICAL,
|
||||||
createdById: 1,
|
createdById: 1,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -13,5 +13,5 @@
|
|||||||
"setupFiles": ["<rootDir>/test/setEnvVars.ts", "jest-date-mock"],
|
"setupFiles": ["<rootDir>/test/setEnvVars.ts", "jest-date-mock"],
|
||||||
"reporters": ["default", "jest-summarizing-reporter"],
|
"reporters": ["default", "jest-summarizing-reporter"],
|
||||||
"workerIdleMemoryLimit": "512MB",
|
"workerIdleMemoryLimit": "512MB",
|
||||||
"maxWorkers": 2
|
"maxWorkers": 8
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"test:cov": "yarn dev:build && jest --coverage",
|
"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: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": "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",
|
"test:e2e:watch": "yarn dev:build && jest --runInBand --detectOpenHandles --forceExit --config ./jest-e2e.json --watch",
|
||||||
"prisma": "yarn workspace @calcom/prisma prisma",
|
"prisma": "yarn workspace @calcom/prisma prisma",
|
||||||
"generate-schemas": "yarn prisma generate && yarn prisma format",
|
"generate-schemas": "yarn prisma generate && yarn prisma format",
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
"@axiomhq/winston": "^1.2.0",
|
"@axiomhq/winston": "^1.2.0",
|
||||||
"@calcom/platform-constants": "*",
|
"@calcom/platform-constants": "*",
|
||||||
"@calcom/platform-enums": "*",
|
"@calcom/platform-enums": "*",
|
||||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.73",
|
"@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-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||||
"@calcom/platform-types": "*",
|
"@calcom/platform-types": "*",
|
||||||
"@calcom/platform-utils": "*",
|
"@calcom/platform-utils": "*",
|
||||||
@@ -59,6 +60,8 @@
|
|||||||
"googleapis": "^84.0.0",
|
"googleapis": "^84.0.0",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
|
"jsforce": "^1.11.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
"nest-winston": "^1.9.4",
|
"nest-winston": "^1.9.4",
|
||||||
"next-auth": "^4.22.1",
|
"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 { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
import { RateLimitRepositoryFixture } from "test/fixtures/repository/rate-limit.repository.fixture";
|
import { RateLimitRepositoryFixture } from "test/fixtures/repository/rate-limit.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.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 { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||||
import { User, PlatformOAuthClient, Team, RateLimit } from "@calcom/prisma/client";
|
import { User, PlatformOAuthClient, Team, RateLimit } from "@calcom/prisma/client";
|
||||||
@@ -24,7 +25,7 @@ describe("AppController", () => {
|
|||||||
let userRepositoryFixture: UserRepositoryFixture;
|
let userRepositoryFixture: UserRepositoryFixture;
|
||||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||||
let rateLimitRepositoryFixture: RateLimitRepositoryFixture;
|
let rateLimitRepositoryFixture: RateLimitRepositoryFixture;
|
||||||
const userEmail = "app-rate-limits-e2e@api.com";
|
const userEmail = `app-rate-limits-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
let organization: Team;
|
let organization: Team;
|
||||||
@@ -94,7 +95,9 @@ describe("AppController", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||||
organization = await organizationsRepositoryFixture.create({ name: "ecorp" });
|
organization = await organizationsRepositoryFixture.create({
|
||||||
|
name: `app-rate-limits-organization-${randomString()}`,
|
||||||
|
});
|
||||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||||
oAuthClient = await createOAuthClient(organization.id);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
profilesRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
CAL_API_VERSION_HEADER,
|
CAL_API_VERSION_HEADER,
|
||||||
X_CAL_CLIENT_ID,
|
X_CAL_CLIENT_ID,
|
||||||
X_CAL_SECRET_KEY,
|
X_CAL_SECRET_KEY,
|
||||||
|
X_CAL_PLATFORM_EMBED,
|
||||||
} from "@calcom/platform-constants";
|
} from "@calcom/platform-constants";
|
||||||
|
|
||||||
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
||||||
@@ -45,6 +46,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
|
|||||||
allowedHeaders: [
|
allowedHeaders: [
|
||||||
X_CAL_CLIENT_ID,
|
X_CAL_CLIENT_ID,
|
||||||
X_CAL_SECRET_KEY,
|
X_CAL_SECRET_KEY,
|
||||||
|
X_CAL_PLATFORM_EMBED,
|
||||||
CAL_API_VERSION_HEADER,
|
CAL_API_VERSION_HEADER,
|
||||||
"Accept",
|
"Accept",
|
||||||
"Authorization",
|
"Authorization",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/
|
|||||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||||
|
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||||
import { UsersModule } from "@/modules/users/users.module";
|
import { UsersModule } from "@/modules/users/users.module";
|
||||||
import { INestApplication } from "@nestjs/common";
|
import { INestApplication } from "@nestjs/common";
|
||||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
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 { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
|
||||||
@@ -35,7 +37,7 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
|||||||
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
let apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||||
let apiKeyString: string;
|
let apiKeyString: string;
|
||||||
|
|
||||||
const userEmail = "bookings-controller-e2e@api.com";
|
const userEmail = `bookings-2024-04-15-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
let eventTypeId: number;
|
let eventTypeId: number;
|
||||||
@@ -67,13 +69,17 @@ describe("Bookings Endpoints 2024-04-15", () => {
|
|||||||
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
const { keyString } = await apiKeysRepositoryFixture.createApiKey(user.id, null);
|
||||||
apiKeyString = keyString;
|
apiKeyString = keyString;
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `bookings-2024-04-15-schedule-${randomString()}-${describe.name}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
eventTypeId = event.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 () => {
|
afterAll(async () => {
|
||||||
await userRepositoryFixture.deleteByEmail(user.email);
|
await userRepositoryFixture.deleteByEmail(user.email);
|
||||||
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
await bookingsRepositoryFixture.deleteAllBookings(user.id, user.email);
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ import {
|
|||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
import { ApiQuery, ApiExcludeController as DocsExcludeController } from "@nestjs/swagger";
|
||||||
import { User } from "@prisma/client";
|
import { User } from "@prisma/client";
|
||||||
|
import { CreationSource } from "@prisma/client";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { NextApiRequest } from "next/types";
|
import { NextApiRequest } from "next/types";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
|
import { X_CAL_CLIENT_ID, X_CAL_PLATFORM_EMBED } from "@calcom/platform-constants";
|
||||||
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
|
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
|
||||||
import {
|
import {
|
||||||
handleNewRecurringBooking,
|
handleNewRecurringBooking,
|
||||||
@@ -161,14 +162,15 @@ export class BookingsController_2024_04_15 {
|
|||||||
async createBooking(
|
async createBooking(
|
||||||
@Req() req: BookingRequest,
|
@Req() req: BookingRequest,
|
||||||
@Body() body: CreateBookingInput_2024_04_15,
|
@Body() body: CreateBookingInput_2024_04_15,
|
||||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||||
|
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||||
): Promise<ApiResponse<Partial<BookingResponse>>> {
|
): Promise<ApiResponse<Partial<BookingResponse>>> {
|
||||||
const oAuthClientId = clientId?.toString();
|
const oAuthClientId = clientId?.toString();
|
||||||
const { orgSlug, locationUrl } = body;
|
const { orgSlug, locationUrl } = body;
|
||||||
req.headers["x-cal-force-slug"] = orgSlug;
|
req.headers["x-cal-force-slug"] = orgSlug;
|
||||||
try {
|
try {
|
||||||
const booking = await handleNewBooking(
|
const booking = await handleNewBooking(
|
||||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl)
|
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed)
|
||||||
);
|
);
|
||||||
if (booking.userId && booking.uid && booking.startTime) {
|
if (booking.userId && booking.uid && booking.startTime) {
|
||||||
void (await this.billingService.increaseUsageByUserId(booking.userId, {
|
void (await this.billingService.increaseUsageByUserId(booking.userId, {
|
||||||
@@ -192,13 +194,16 @@ export class BookingsController_2024_04_15 {
|
|||||||
@Req() req: BookingRequest,
|
@Req() req: BookingRequest,
|
||||||
@Param("bookingId") bookingId: string,
|
@Param("bookingId") bookingId: string,
|
||||||
@Body() _: CancelBookingInput_2024_04_15,
|
@Body() _: CancelBookingInput_2024_04_15,
|
||||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||||
|
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||||
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
|
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
|
||||||
const oAuthClientId = clientId?.toString();
|
const oAuthClientId = clientId?.toString();
|
||||||
if (bookingId) {
|
if (bookingId) {
|
||||||
try {
|
try {
|
||||||
req.body.id = parseInt(bookingId);
|
req.body.id = parseInt(bookingId);
|
||||||
const res = await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
|
const res = await handleCancelBooking(
|
||||||
|
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||||
|
);
|
||||||
if (!res.onlyRemovedAttendee) {
|
if (!res.onlyRemovedAttendee) {
|
||||||
void (await this.billingService.cancelUsageByBookingUid(res.bookingUid));
|
void (await this.billingService.cancelUsageByBookingUid(res.bookingUid));
|
||||||
}
|
}
|
||||||
@@ -246,7 +251,8 @@ export class BookingsController_2024_04_15 {
|
|||||||
async createRecurringBooking(
|
async createRecurringBooking(
|
||||||
@Req() req: BookingRequest,
|
@Req() req: BookingRequest,
|
||||||
@Body() _: CreateRecurringBookingInput_2024_04_15[],
|
@Body() _: CreateRecurringBookingInput_2024_04_15[],
|
||||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||||
|
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||||
): Promise<ApiResponse<BookingResponse[]>> {
|
): Promise<ApiResponse<BookingResponse[]>> {
|
||||||
const oAuthClientId = clientId?.toString();
|
const oAuthClientId = clientId?.toString();
|
||||||
try {
|
try {
|
||||||
@@ -258,7 +264,7 @@ export class BookingsController_2024_04_15 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
|
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
|
||||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId)
|
await this.createNextApiRecurringBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||||
);
|
);
|
||||||
|
|
||||||
createdBookings.forEach(async (booking) => {
|
createdBookings.forEach(async (booking) => {
|
||||||
@@ -284,13 +290,14 @@ export class BookingsController_2024_04_15 {
|
|||||||
async createInstantBooking(
|
async createInstantBooking(
|
||||||
@Req() req: BookingRequest,
|
@Req() req: BookingRequest,
|
||||||
@Body() _: CreateBookingInput_2024_04_15,
|
@Body() _: CreateBookingInput_2024_04_15,
|
||||||
@Headers(X_CAL_CLIENT_ID) clientId?: string
|
@Headers(X_CAL_CLIENT_ID) clientId?: string,
|
||||||
|
@Headers(X_CAL_PLATFORM_EMBED) isEmbed?: string
|
||||||
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
|
): Promise<ApiResponse<Awaited<ReturnType<typeof handleInstantMeeting>>>> {
|
||||||
const oAuthClientId = clientId?.toString();
|
const oAuthClientId = clientId?.toString();
|
||||||
req.userId = (await this.getOwnerId(req)) ?? -1;
|
req.userId = (await this.getOwnerId(req)) ?? -1;
|
||||||
try {
|
try {
|
||||||
const instantMeeting = await handleInstantMeeting(
|
const instantMeeting = await handleInstantMeeting(
|
||||||
await this.createNextApiBookingRequest(req, oAuthClientId)
|
await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (instantMeeting.userId && instantMeeting.bookingUid) {
|
if (instantMeeting.userId && instantMeeting.bookingUid) {
|
||||||
@@ -332,8 +339,14 @@ export class BookingsController_2024_04_15 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getOAuthClientsParams(clientId: string): Promise<OAuthRequestParams> {
|
private async getOAuthClientsParams(clientId: string, isEmbed = false): Promise<OAuthRequestParams> {
|
||||||
const res = { ...DEFAULT_PLATFORM_PARAMS };
|
const res = { ...DEFAULT_PLATFORM_PARAMS };
|
||||||
|
|
||||||
|
if (isEmbed) {
|
||||||
|
// embed should ignore oauth client settings and enable emails by default
|
||||||
|
return { ...res, arePlatformEmailsEnabled: true };
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const client = await this.oAuthClientRepository.getOAuthClient(clientId);
|
const client = await this.oAuthClientRepository.getOAuthClient(clientId);
|
||||||
// fetch oAuthClient from db and use data stored in db to set these values
|
// fetch oAuthClient from db and use data stored in db to set these values
|
||||||
@@ -354,13 +367,14 @@ export class BookingsController_2024_04_15 {
|
|||||||
private async createNextApiBookingRequest(
|
private async createNextApiBookingRequest(
|
||||||
req: BookingRequest,
|
req: BookingRequest,
|
||||||
oAuthClientId?: string,
|
oAuthClientId?: string,
|
||||||
platformBookingLocation?: string
|
platformBookingLocation?: string,
|
||||||
|
isEmbed?: string
|
||||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||||
const requestId = req.get("X-Request-Id");
|
const requestId = req.get("X-Request-Id");
|
||||||
const clone = { ...req };
|
const clone = { ...req };
|
||||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||||
const oAuthParams = oAuthClientId
|
const oAuthParams = oAuthClientId
|
||||||
? await this.getOAuthClientsParams(oAuthClientId)
|
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||||
: DEFAULT_PLATFORM_PARAMS;
|
: DEFAULT_PLATFORM_PARAMS;
|
||||||
this.logger.log(`createNextApiBookingRequest_2024_04_15`, {
|
this.logger.log(`createNextApiBookingRequest_2024_04_15`, {
|
||||||
requestId,
|
requestId,
|
||||||
@@ -370,19 +384,24 @@ export class BookingsController_2024_04_15 {
|
|||||||
...oAuthParams,
|
...oAuthParams,
|
||||||
});
|
});
|
||||||
Object.assign(clone, { userId, ...oAuthParams, platformBookingLocation });
|
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;
|
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createNextApiRecurringBookingRequest(
|
private async createNextApiRecurringBookingRequest(
|
||||||
req: BookingRequest,
|
req: BookingRequest,
|
||||||
oAuthClientId?: string,
|
oAuthClientId?: string,
|
||||||
platformBookingLocation?: string
|
platformBookingLocation?: string,
|
||||||
|
isEmbed?: string
|
||||||
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
): Promise<NextApiRequest & { userId?: number } & OAuthRequestParams> {
|
||||||
const clone = { ...req };
|
const clone = { ...req };
|
||||||
const userId = (await this.getOwnerId(req)) ?? -1;
|
const userId = (await this.getOwnerId(req)) ?? -1;
|
||||||
const oAuthParams = oAuthClientId
|
const oAuthParams = oAuthClientId
|
||||||
? await this.getOAuthClientsParams(oAuthClientId)
|
? await this.getOAuthClientsParams(oAuthClientId, this.transformToBoolean(isEmbed))
|
||||||
: DEFAULT_PLATFORM_PARAMS;
|
: DEFAULT_PLATFORM_PARAMS;
|
||||||
const requestId = req.get("X-Request-Id");
|
const requestId = req.get("X-Request-Id");
|
||||||
this.logger.log(`createNextApiRecurringBookingRequest_2024_04_15`, {
|
this.logger.log(`createNextApiRecurringBookingRequest_2024_04_15`, {
|
||||||
@@ -397,6 +416,7 @@ export class BookingsController_2024_04_15 {
|
|||||||
...oAuthParams,
|
...oAuthParams,
|
||||||
platformBookingLocation,
|
platformBookingLocation,
|
||||||
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
noEmail: !oAuthParams.arePlatformEmailsEnabled,
|
||||||
|
creationSource: CreationSource.API_V2,
|
||||||
});
|
});
|
||||||
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
return clone as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
|
||||||
}
|
}
|
||||||
@@ -424,4 +444,8 @@ export class BookingsController_2024_04_15 {
|
|||||||
|
|
||||||
throw new InternalServerErrorException(errMsg);
|
throw new InternalServerErrorException(errMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private transformToBoolean(v?: string): boolean {
|
||||||
|
return v && typeof v === "string" ? v.toLowerCase() === "true" : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger";
|
import { ApiProperty, ApiPropertyOptional, ApiHideProperty } from "@nestjs/swagger";
|
||||||
import { Transform, Type } from "class-transformer";
|
import { Transform, Type } from "class-transformer";
|
||||||
import {
|
import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
@@ -126,4 +126,62 @@ export class CreateBookingInput_2024_04_15 {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
@ApiPropertyOptional()
|
@ApiPropertyOptional()
|
||||||
locationUrl?: string;
|
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,
|
ApiKeyRepository,
|
||||||
],
|
],
|
||||||
controllers: [BookingsController_2024_08_13],
|
controllers: [BookingsController_2024_08_13],
|
||||||
|
exports: [InputBookingsService_2024_08_13, OutputBookingsService_2024_08_13, BookingsService_2024_08_13],
|
||||||
})
|
})
|
||||||
export class BookingsModule_2024_08_13 {}
|
export class BookingsModule_2024_08_13 {}
|
||||||
@@ -5,7 +5,7 @@ import { MarkAbsentBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/out
|
|||||||
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
||||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||||
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
import { BookingsService_2024_08_13 } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
||||||
import { VERSION_2024_08_13_VALUE } from "@/lib/api-versions";
|
import { VERSION_2024_08_13_VALUE, VERSION_2024_08_13 } from "@/lib/api-versions";
|
||||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||||
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
|
||||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||||
@@ -65,6 +65,7 @@ import {
|
|||||||
@ApiHeader({
|
@ApiHeader({
|
||||||
name: "cal-api-version",
|
name: "cal-api-version",
|
||||||
description: `Must be set to \`2024-08-13\``,
|
description: `Must be set to \`2024-08-13\``,
|
||||||
|
example: VERSION_2024_08_13,
|
||||||
required: true,
|
required: true,
|
||||||
})
|
})
|
||||||
export class BookingsController_2024_08_13 {
|
export class BookingsController_2024_08_13 {
|
||||||
|
|||||||
+12
-4
@@ -19,6 +19,7 @@ import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.rep
|
|||||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 apiKeysRepositoryFixture: ApiKeysRepositoryFixture;
|
||||||
let apiKeyString: string;
|
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 user: User;
|
||||||
|
|
||||||
let eventTypeId: number;
|
let eventTypeId: number;
|
||||||
|
const eventTypeSlug = `api-key-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
|
|
||||||
let createdBooking: BookingOutput_2024_08_13;
|
let createdBooking: BookingOutput_2024_08_13;
|
||||||
let rescheduledBooking: 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);
|
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
||||||
apiKeysRepositoryFixture = new ApiKeysRepositoryFixture(moduleRef);
|
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({
|
user = await userRepositoryFixture.create({
|
||||||
email: userEmail,
|
email: userEmail,
|
||||||
@@ -110,13 +114,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
apiKeyString = keyString;
|
apiKeyString = keyString;
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
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",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
eventTypeId = event.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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.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 { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||||
@@ -26,9 +26,7 @@ import {
|
|||||||
GetSeatedBookingOutput_2024_08_13,
|
GetSeatedBookingOutput_2024_08_13,
|
||||||
} from "@calcom/platform-types";
|
} from "@calcom/platform-types";
|
||||||
import { BookingOutput_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";
|
import { Booking, PlatformOAuthClient, Team, User, EventType } from "@calcom/prisma/client";
|
||||||
|
|
||||||
const suffix = randomNumber();
|
|
||||||
|
|
||||||
describe("Bookings Endpoints 2024-08-13", () => {
|
describe("Bookings Endpoints 2024-08-13", () => {
|
||||||
describe("Booking fields", () => {
|
describe("Booking fields", () => {
|
||||||
@@ -43,7 +41,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let oAuthClient: PlatformOAuthClient;
|
let oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = `alice-${suffix}@api.com`;
|
const userEmail = `booking-fields-2024-08-13-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
let bookingWithSplitName: Booking;
|
let bookingWithSplitName: Booking;
|
||||||
@@ -54,7 +52,12 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
|
|
||||||
let seatedBookingWithSplitName: Booking;
|
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 () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -76,7 +79,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -89,109 +94,36 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `booking-fields-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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",
|
title: `booking-fields-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: `with-custom-booking-fields-${randomNumber()}`,
|
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,
|
length: 60,
|
||||||
bookingFields: [
|
bookingFields: [
|
||||||
{
|
{
|
||||||
@@ -381,11 +313,93 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
},
|
},
|
||||||
user.id
|
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();
|
app = moduleRef.createNestApplication();
|
||||||
bootstrap(app as NestExpressApplication);
|
bootstrap(app as NestExpressApplication);
|
||||||
|
|
||||||
await app.init();
|
await app.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -441,7 +455,6 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
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].name).toEqual(`${splitName.firstName} ${splitName.lastName}`);
|
||||||
expect(data.attendees[0].bookingFieldsResponses.name).toEqual(splitName);
|
expect(data.attendees[0].bookingFieldsResponses.name).toEqual(splitName);
|
||||||
} else {
|
} 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 () => {
|
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 = {
|
const body: CreateBookingInput_2024_08_13 = {
|
||||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||||
eventTypeId: eventTypeWithBookingFieldsId,
|
eventTypeId: eventTypeWithBookingFields.id,
|
||||||
attendee: {
|
attendee: {
|
||||||
name: "Mr Proper",
|
name: "Mr Proper",
|
||||||
email: "mr_proper@gmail.com",
|
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 () => {
|
it("should be able to book an event type with custom required booking fields", async () => {
|
||||||
const body: CreateBookingInput_2024_08_13 = {
|
const body: CreateBookingInput_2024_08_13 = {
|
||||||
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
start: new Date(Date.UTC(2030, 0, 8, 13, 0, 0)).toISOString(),
|
||||||
eventTypeId: eventTypeWithBookingFieldsId,
|
eventTypeId: eventTypeWithBookingFields.id,
|
||||||
attendee: {
|
attendee: {
|
||||||
name: "Mr Proper",
|
name: "Mr Proper",
|
||||||
email: "mr_proper@gmail.com",
|
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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
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 user: User;
|
||||||
|
|
||||||
let eventTypeId: number;
|
let eventTypeId: number;
|
||||||
const eventTypeSlug = "peer-coding";
|
const eventTypeSlug = `confirm-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
|
|
||||||
let createdBooking1: Booking;
|
let createdBooking1: Booking;
|
||||||
let createdBooking2: Booking;
|
let createdBooking2: Booking;
|
||||||
@@ -64,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -77,13 +80,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `confirm-bookings-2024-08-13-${randomString()}-schedule`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
eventTypeId = event.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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 emailsEnabledSetup: EmailSetup;
|
||||||
let emailsDisabledSetup: 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 () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -114,7 +115,9 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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 setupEnabledEmails();
|
||||||
await setupDisabledEmails();
|
await setupDisabledEmails();
|
||||||
@@ -139,7 +142,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
|||||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||||
|
|
||||||
const user = await userRepositoryFixture.create({
|
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: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsEnabled.id,
|
id: oAuthClientEmailsEnabled.id,
|
||||||
@@ -148,14 +151,19 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
|
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,7 +179,7 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
|||||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||||
|
|
||||||
const user = await userRepositoryFixture.create({
|
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: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsDisabled.id,
|
id: oAuthClientEmailsDisabled.id,
|
||||||
@@ -179,13 +187,18 @@ describe("Bookings Endpoints 2024-08-13 confirm emails", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `confirm-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
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 { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 emailsEnabledSetup: EmailSetup;
|
||||||
let emailsDisabledSetup: EmailSetup;
|
let emailsDisabledSetup: EmailSetup;
|
||||||
|
|
||||||
const authEmail = "admin@example.com";
|
const authEmail = "team-emails-2024-08-13-user-admin@example.com";
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -128,7 +129,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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 setupEnabledEmails();
|
||||||
await setupDisabledEmails();
|
await setupDisabledEmails();
|
||||||
@@ -152,7 +153,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||||
|
|
||||||
const team = await teamRepositoryFixture.create({
|
const team = await teamRepositoryFixture.create({
|
||||||
name: "team 1",
|
name: `team-emails-team-${randomString()}`,
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
createdByOAuthClient: {
|
createdByOAuthClient: {
|
||||||
@@ -163,7 +164,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const member1 = await userRepositoryFixture.create({
|
const member1 = await userRepositoryFixture.create({
|
||||||
email: "alice@gmail.com",
|
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||||
platformOAuthClients: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsEnabled.id,
|
id: oAuthClientEmailsEnabled.id,
|
||||||
@@ -172,7 +173,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const member2 = await userRepositoryFixture.create({
|
const member2 = await userRepositoryFixture.create({
|
||||||
email: "bob@gmail.com",
|
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||||
platformOAuthClients: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsEnabled.id,
|
id: oAuthClientEmailsEnabled.id,
|
||||||
@@ -181,7 +182,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -237,8 +238,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
team: {
|
team: {
|
||||||
connect: { id: team.id },
|
connect: { id: team.id },
|
||||||
},
|
},
|
||||||
title: "Collective Event Type",
|
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "collective-event-type",
|
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: true,
|
assignAllTeamMembers: true,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -278,8 +279,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
team: {
|
team: {
|
||||||
connect: { id: team.id },
|
connect: { id: team.id },
|
||||||
},
|
},
|
||||||
title: "Round Robin Event Type",
|
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "round-robin-event-type",
|
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: false,
|
assignAllTeamMembers: false,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -336,7 +337,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
const oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||||
|
|
||||||
const team = await teamRepositoryFixture.create({
|
const team = await teamRepositoryFixture.create({
|
||||||
name: "team 2",
|
name: `team-emails-2024-08-13-team-${randomString()}`,
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
createdByOAuthClient: {
|
createdByOAuthClient: {
|
||||||
@@ -347,7 +348,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const member1 = await userRepositoryFixture.create({
|
const member1 = await userRepositoryFixture.create({
|
||||||
email: "charlie@gmail.com",
|
email: `team-emails-2024-08-13-member1-${randomString()}@api.com`,
|
||||||
platformOAuthClients: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsDisabled.id,
|
id: oAuthClientEmailsDisabled.id,
|
||||||
@@ -356,7 +357,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const member2 = await userRepositoryFixture.create({
|
const member2 = await userRepositoryFixture.create({
|
||||||
email: "dean@gmail.com",
|
email: `team-emails-2024-08-13-member2-${randomString()}@api.com`,
|
||||||
platformOAuthClients: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsDisabled.id,
|
id: oAuthClientEmailsDisabled.id,
|
||||||
@@ -365,7 +366,7 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `team-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -421,8 +422,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
team: {
|
team: {
|
||||||
connect: { id: team.id },
|
connect: { id: team.id },
|
||||||
},
|
},
|
||||||
title: "Collective Event Type",
|
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "collective-event-type",
|
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: true,
|
assignAllTeamMembers: true,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -462,8 +463,8 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
team: {
|
team: {
|
||||||
connect: { id: team.id },
|
connect: { id: team.id },
|
||||||
},
|
},
|
||||||
title: "Round Robin Event Type",
|
title: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "round-robin-event-type",
|
slug: `team-emails-2024-08-13-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: false,
|
assignAllTeamMembers: false,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -965,17 +966,19 @@ describe("Bookings Endpoints 2024-08-13 team emails", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await teamRepositoryFixture.delete(organization.id);
|
await teamRepositoryFixture.delete(organization.id);
|
||||||
|
await userRepositoryFixture.deleteByEmail(authEmail);
|
||||||
await userRepositoryFixture.deleteByEmail(emailsEnabledSetup.member1.email);
|
await userRepositoryFixture.deleteByEmail(emailsEnabledSetup.member1.email);
|
||||||
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member1.email);
|
await userRepositoryFixture.deleteByEmail(emailsDisabledSetup.member2.email);
|
||||||
await bookingsRepositoryFixture.deleteAllBookings(
|
await bookingsRepositoryFixture.deleteAllBookings(
|
||||||
emailsEnabledSetup.member1.id,
|
emailsEnabledSetup.member1.id,
|
||||||
emailsEnabledSetup.member1.email
|
emailsEnabledSetup.member1.email
|
||||||
);
|
);
|
||||||
await bookingsRepositoryFixture.deleteAllBookings(
|
await bookingsRepositoryFixture.deleteAllBookings(
|
||||||
emailsDisabledSetup.member1.id,
|
emailsDisabledSetup.member1.id,
|
||||||
emailsDisabledSetup.member1.email
|
emailsDisabledSetup.member2.email
|
||||||
);
|
);
|
||||||
await app.close();
|
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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.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 { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||||
import {
|
import {
|
||||||
@@ -101,7 +102,9 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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 setupEnabledEmails();
|
||||||
await setupDisabledEmails();
|
await setupDisabledEmails();
|
||||||
@@ -116,7 +119,7 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
|||||||
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
const oAuthClientEmailsEnabled = await createOAuthClient(organization.id, true);
|
||||||
|
|
||||||
const user = await userRepositoryFixture.create({
|
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: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsEnabled.id,
|
id: oAuthClientEmailsEnabled.id,
|
||||||
@@ -125,22 +128,26 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
|
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
slug: "peer-coding-recurring",
|
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
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 oAuthClientEmailsDisabled = await createOAuthClient(organization.id, false);
|
||||||
|
|
||||||
const user = await userRepositoryFixture.create({
|
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: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClientEmailsDisabled.id,
|
id: oAuthClientEmailsDisabled.id,
|
||||||
@@ -168,21 +175,25 @@ describe("Bookings Endpoints 2024-08-13 user emails", () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `user-emails-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
slug: "peer-coding-recurring",
|
slug: `user-emails-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
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 { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||||
|
|
||||||
const teamUserEmail = "orgUser1team1@api.com";
|
const teamUserEmail = `reassign-bookings-2024-08-13-user1-${randomString()}@api.com`;
|
||||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
const teamUserEmail2 = `reassign-bookings-2024-08-13-user2-${randomString()}@api.com`;
|
||||||
let teamUser1: User;
|
let teamUser1: User;
|
||||||
let teamUser2: User;
|
let teamUser2: User;
|
||||||
|
|
||||||
@@ -78,11 +79,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
team = await teamRepositoryFixture.create({
|
team = await teamRepositoryFixture.create({
|
||||||
name: "team 1",
|
name: `reassign-bookings-2024-08-13-team-${randomString()}`,
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
createdByOAuthClient: {
|
createdByOAuthClient: {
|
||||||
@@ -95,17 +98,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamUser1 = await userRepositoryFixture.create({
|
teamUser1 = await userRepositoryFixture.create({
|
||||||
email: teamUserEmail,
|
email: teamUserEmail,
|
||||||
locale: "it",
|
locale: "it",
|
||||||
name: "orgUser1team1",
|
name: `reassign-bookings-2024-08-13-user1-${randomString()}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
teamUser2 = await userRepositoryFixture.create({
|
teamUser2 = await userRepositoryFixture.create({
|
||||||
email: teamUserEmail2,
|
email: teamUserEmail2,
|
||||||
locale: "it",
|
locale: "it",
|
||||||
name: "orgUser2team1",
|
name: `reassign-bookings-2024-08-13-user2-${randomString()}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `reassign-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -164,8 +167,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
users: {
|
users: {
|
||||||
connect: [{ id: teamUser1.id }, { id: teamUser2.id }],
|
connect: [{ id: teamUser1.id }, { id: teamUser2.id }],
|
||||||
},
|
},
|
||||||
title: "Round Robin Event Type",
|
title: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "round-robin-event-type",
|
slug: `reassign-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: false,
|
assignAllTeamMembers: false,
|
||||||
bookingFields: [],
|
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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "bookings-controller-e2e@api.com";
|
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
const maxRecurrenceCount = 3;
|
const maxRecurrenceCount = 3;
|
||||||
let recurringEventTypeId: number;
|
let recurringEventTypeId: number;
|
||||||
|
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
|
let createdRecurringBooking: RecurringBookingOutput_2024_08_13[];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -67,7 +70,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -80,7 +85,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -89,8 +94,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "peer-coding-recurring",
|
slug: recurringEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||||
},
|
},
|
||||||
@@ -344,7 +349,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let oAuthClient: PlatformOAuthClient;
|
let oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "bookings-controller-e2e@api.com";
|
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
const maxRecurrenceCount = 4;
|
const maxRecurrenceCount = 4;
|
||||||
@@ -380,7 +385,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -393,17 +400,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
|
|
||||||
|
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "peer-coding-recurring",
|
slug: recurringEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||||
},
|
},
|
||||||
@@ -551,7 +559,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let oAuthClient: PlatformOAuthClient;
|
let oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "bookings-controller-e2e@api.com";
|
const userEmail = `recurring-bookings-2024-08-13-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
const maxRecurrenceCount = 4;
|
const maxRecurrenceCount = 4;
|
||||||
@@ -587,7 +595,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -600,17 +610,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `recurring-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
|
|
||||||
|
const recurringEventSlug = `recurring-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
const recurringEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `recurring-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "peer-coding-recurring",
|
slug: recurringEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||||
},
|
},
|
||||||
|
|||||||
+53
-46
@@ -19,6 +19,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
|||||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
const seatedTventTypeSlug = "peer-coding-seated";
|
|
||||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
|
||||||
let seatedEventTypeId: number;
|
let seatedEventTypeId: number;
|
||||||
let recurringSeatedEventTypeId: number;
|
let recurringSeatedEventTypeId: number;
|
||||||
const maxRecurrenceCount = 3;
|
const maxRecurrenceCount = 3;
|
||||||
|
|
||||||
|
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||||
|
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||||
|
|
||||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||||
|
|
||||||
const emailAttendeeOne = "mr_proper_seated@gmail.com";
|
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||||
const nameAttendeeOne = "Mr Proper Seated";
|
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||||
const emailAttendeeTwo = "mr_proper_friend_seated@gmail.com";
|
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||||
const nameAttendeeTwo = "Mr Proper Friend Seated";
|
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -88,7 +90,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -101,15 +105,15 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||||
{
|
{
|
||||||
title: "peer coding",
|
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
seatsPerTimeSlot: 5,
|
seatsPerTimeSlot: 5,
|
||||||
seatsShowAttendees: true,
|
seatsShowAttendees: true,
|
||||||
@@ -123,8 +127,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||||
seatsPerTimeSlot: 5,
|
seatsPerTimeSlot: 5,
|
||||||
@@ -200,7 +204,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||||
expect(data.eventType).toEqual({
|
expect(data.eventType).toEqual({
|
||||||
id: seatedEventTypeId,
|
id: seatedEventTypeId,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(data.attendees.length).toEqual(1);
|
expect(data.attendees.length).toEqual(1);
|
||||||
expect(data.attendees[0]).toEqual({
|
expect(data.attendees[0]).toEqual({
|
||||||
@@ -272,7 +276,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||||
expect(data.eventType).toEqual({
|
expect(data.eventType).toEqual({
|
||||||
id: seatedEventTypeId,
|
id: seatedEventTypeId,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(data.attendees.length).toEqual(2);
|
expect(data.attendees.length).toEqual(2);
|
||||||
// note(Lauris): first attendee is from previous test request
|
// 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.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(firstBooking.eventType).toEqual({
|
expect(firstBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(firstBooking.attendees.length).toEqual(1);
|
expect(firstBooking.attendees.length).toEqual(1);
|
||||||
expect(firstBooking.attendees[0]).toEqual({
|
expect(firstBooking.attendees[0]).toEqual({
|
||||||
@@ -401,7 +405,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(secondBooking.eventType).toEqual({
|
expect(secondBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(secondBooking.attendees.length).toEqual(1);
|
expect(secondBooking.attendees.length).toEqual(1);
|
||||||
expect(secondBooking.attendees[0]).toEqual({
|
expect(secondBooking.attendees[0]).toEqual({
|
||||||
@@ -575,7 +579,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
expect(data.eventTypeId).toEqual(seatedEventTypeId);
|
||||||
expect(data.eventType).toEqual({
|
expect(data.eventType).toEqual({
|
||||||
id: seatedEventTypeId,
|
id: seatedEventTypeId,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(data.attendees.length).toEqual(1);
|
expect(data.attendees.length).toEqual(1);
|
||||||
const attendee = createdSeatedBooking.attendees.find((a) => a.seatUid === body.seatUid);
|
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.eventTypeId).toEqual(seatedEventTypeId);
|
||||||
expect(data.eventType).toEqual({
|
expect(data.eventType).toEqual({
|
||||||
id: seatedEventTypeId,
|
id: seatedEventTypeId,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(data.attendees.length).toEqual(0);
|
expect(data.attendees.length).toEqual(0);
|
||||||
expect(data.location).toBeDefined();
|
expect(data.location).toBeDefined();
|
||||||
@@ -681,22 +685,23 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let oAuthClient: PlatformOAuthClient;
|
let oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "seated-bookings-controller-e2e@api.com";
|
const userEmail = `seated-bookings-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
const seatedTventTypeSlug = "peer-coding-seated";
|
|
||||||
const recurringSeatedTventTypeSlug = "peer-coding-recurring-seated";
|
|
||||||
let seatedEventTypeId: number;
|
let seatedEventTypeId: number;
|
||||||
let recurringSeatedEventTypeId: number;
|
let recurringSeatedEventTypeId: number;
|
||||||
const maxRecurrenceCount = 3;
|
const maxRecurrenceCount = 3;
|
||||||
|
|
||||||
|
const seatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||||
|
const recurringSeatedEventSlug = `seated-bookings-event-type-${randomString()}`;
|
||||||
|
|
||||||
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
let createdSeatedBooking: CreateSeatedBookingOutput_2024_08_13;
|
||||||
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
let createdRecurringSeatedBooking: CreateRecurringSeatedBookingOutput_2024_08_13[];
|
||||||
|
|
||||||
const emailAttendeeOne = "mr_proper_first@gmail.com";
|
const emailAttendeeOne = `seated-bookings-attendee1-${randomString()}@api.com`;
|
||||||
const nameAttendeeOne = "Mr Proper First";
|
const nameAttendeeOne = `Attendee One ${randomString()}`;
|
||||||
const emailAttendeeTwo = "mr_proper_second@gmail.com";
|
const emailAttendeeTwo = `seated-bookings-attendee2-${randomString()}@api.com`;
|
||||||
const nameAttendeeTwo = "Mr Proper Second";
|
const nameAttendeeTwo = `Attendee Two ${randomString()}`;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -718,7 +723,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -731,20 +738,17 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `seated-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
const seatedEvent = await eventTypesRepositoryFixture.create(
|
const seatedEvent = await eventTypesRepositoryFixture.create(
|
||||||
{
|
{
|
||||||
title: "peer coding",
|
title: `seated-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: seatedTventTypeSlug,
|
slug: seatedEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
seatsPerTimeSlot: 5,
|
seatsPerTimeSlot: 3,
|
||||||
seatsShowAttendees: true,
|
|
||||||
seatsShowAvailabilityCount: true,
|
|
||||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
|
||||||
},
|
},
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
@@ -753,14 +757,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
const recurringSeatedEvent = await eventTypesRepositoryFixture.create(
|
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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: `seated-bookings-2024-08-13-recurring-event-type-${randomString()}`,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
|
seatsPerTimeSlot: 3,
|
||||||
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
recurringEvent: { freq: 2, count: maxRecurrenceCount, interval: 1 },
|
||||||
seatsPerTimeSlot: 5,
|
|
||||||
seatsShowAttendees: true,
|
|
||||||
seatsShowAvailabilityCount: true,
|
|
||||||
locations: [{ type: "inPerson", address: "via 10, rome, italy" }],
|
|
||||||
},
|
},
|
||||||
user.id
|
user.id
|
||||||
);
|
);
|
||||||
@@ -836,7 +837,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(firstBooking.eventType).toEqual({
|
expect(firstBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(firstBooking.attendees.length).toEqual(1);
|
expect(firstBooking.attendees.length).toEqual(1);
|
||||||
expect(firstBooking.attendees[0]).toEqual({
|
expect(firstBooking.attendees[0]).toEqual({
|
||||||
@@ -871,7 +872,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(secondBooking.eventType).toEqual({
|
expect(secondBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(secondBooking.attendees.length).toEqual(1);
|
expect(secondBooking.attendees.length).toEqual(1);
|
||||||
expect(secondBooking.attendees[0]).toEqual({
|
expect(secondBooking.attendees[0]).toEqual({
|
||||||
@@ -949,10 +950,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
expect(firstBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(firstBooking.eventType).toEqual({
|
expect(firstBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(firstBooking.attendees.length).toEqual(2);
|
expect(firstBooking.attendees.length).toEqual(2);
|
||||||
expect(firstBooking.attendees[0]).toEqual({
|
const firstAttendee = firstBooking.attendees.find(
|
||||||
|
(attendee) => attendee.name === nameAttendeeOne
|
||||||
|
);
|
||||||
|
expect(firstAttendee).toEqual({
|
||||||
name: nameAttendeeOne,
|
name: nameAttendeeOne,
|
||||||
email: emailAttendeeOne,
|
email: emailAttendeeOne,
|
||||||
timeZone: body.attendee.timeZone,
|
timeZone: body.attendee.timeZone,
|
||||||
@@ -965,7 +969,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
},
|
},
|
||||||
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
|
metadata: createdRecurringSeatedBooking[0].attendees[0].metadata,
|
||||||
});
|
});
|
||||||
expect(firstBooking.attendees[1]).toEqual({
|
const secondAttendee = firstBooking.attendees.find(
|
||||||
|
(attendee) => attendee.name === nameAttendeeTwo
|
||||||
|
);
|
||||||
|
expect(secondAttendee).toEqual({
|
||||||
name: nameAttendeeTwo,
|
name: nameAttendeeTwo,
|
||||||
email: emailAttendeeTwo,
|
email: emailAttendeeTwo,
|
||||||
timeZone: body.attendee.timeZone,
|
timeZone: body.attendee.timeZone,
|
||||||
@@ -997,7 +1004,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
expect(secondBooking.eventTypeId).toEqual(recurringSeatedEventTypeId);
|
||||||
expect(secondBooking.eventType).toEqual({
|
expect(secondBooking.eventType).toEqual({
|
||||||
id: recurringSeatedEventTypeId,
|
id: recurringSeatedEventTypeId,
|
||||||
slug: recurringSeatedTventTypeSlug,
|
slug: recurringSeatedEventSlug,
|
||||||
});
|
});
|
||||||
expect(secondBooking.attendees.length).toEqual(2);
|
expect(secondBooking.attendees.length).toEqual(2);
|
||||||
expect(secondBooking.attendees[0]).toEqual({
|
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 { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||||
@@ -52,13 +53,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
let organizationsRepositoryFixture: OrganizationRepositoryFixture;
|
||||||
let profileRepositoryFixture: ProfileRepositoryFixture;
|
let profileRepositoryFixture: ProfileRepositoryFixture;
|
||||||
|
|
||||||
const teamUserEmail = "orgUser1team1@api.com";
|
const teamUserEmail = `team-bookings-user1-${randomString()}@api.com`;
|
||||||
const teamUserEmail2 = "orgUser2team1@api.com";
|
const teamUserEmail2 = `team-bookings-user2-${randomString()}@api.com`;
|
||||||
let teamUser: User;
|
let teamUser: User;
|
||||||
let teamUser2: User;
|
let teamUser2: User;
|
||||||
|
|
||||||
let team1EventTypeId: number;
|
let team1EventTypeId: number;
|
||||||
let team2EventTypeId: number;
|
let team2EventTypeId: number;
|
||||||
|
let phoneOnlyEventTypeId: number;
|
||||||
|
|
||||||
|
const team1EventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||||
|
const team2EventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||||
|
const phoneOnlyEventTypeSlug = `team-bookings-event-type-${randomString()}`;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -84,11 +90,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
hostsRepositoryFixture = new HostsRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
team1 = await teamRepositoryFixture.create({
|
team1 = await teamRepositoryFixture.create({
|
||||||
name: "team 1",
|
name: `team-bookings-team1-${randomString()}`,
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
createdByOAuthClient: {
|
createdByOAuthClient: {
|
||||||
@@ -99,7 +107,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
team2 = await teamRepositoryFixture.create({
|
team2 = await teamRepositoryFixture.create({
|
||||||
name: "team 2",
|
name: `team-bookings-team2-${randomString()}`,
|
||||||
isOrganization: false,
|
isOrganization: false,
|
||||||
parent: { connect: { id: organization.id } },
|
parent: { connect: { id: organization.id } },
|
||||||
createdByOAuthClient: {
|
createdByOAuthClient: {
|
||||||
@@ -132,7 +140,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `team-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -195,8 +203,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
team: {
|
team: {
|
||||||
connect: { id: team1.id },
|
connect: { id: team1.id },
|
||||||
},
|
},
|
||||||
title: "Collective Event Type",
|
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "collective-event-type",
|
slug: team1EventTypeSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: true,
|
assignAllTeamMembers: true,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -205,13 +213,90 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
|
|
||||||
team1EventTypeId = team1EventType.id;
|
team1EventTypeId = team1EventType.id;
|
||||||
|
|
||||||
|
const phoneOnlyEventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||||
|
schedulingType: "ROUND_ROBIN",
|
||||||
|
team: {
|
||||||
|
connect: { id: team1.id },
|
||||||
|
},
|
||||||
|
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
|
slug: phoneOnlyEventTypeSlug,
|
||||||
|
length: 15,
|
||||||
|
assignAllTeamMembers: false,
|
||||||
|
hosts: {
|
||||||
|
connectOrCreate: [
|
||||||
|
{
|
||||||
|
where: {
|
||||||
|
userId_eventTypeId: {
|
||||||
|
userId: teamUser.id,
|
||||||
|
eventTypeId: team1EventTypeId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
userId: teamUser.id,
|
||||||
|
isFixed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
bookingFields: [
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
type: "name",
|
||||||
|
label: "your name",
|
||||||
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||||
|
variant: "fullName",
|
||||||
|
editable: "system",
|
||||||
|
required: true,
|
||||||
|
defaultLabel: "your_name",
|
||||||
|
variantsConfig: {
|
||||||
|
variants: {
|
||||||
|
fullName: {
|
||||||
|
fields: [{ name: "fullName", type: "text", label: "your name", required: true }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
type: "email",
|
||||||
|
label: "your email",
|
||||||
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||||
|
editable: "system",
|
||||||
|
required: false,
|
||||||
|
defaultLabel: "email_address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "attendeePhoneNumber",
|
||||||
|
type: "phone",
|
||||||
|
label: "phone_number",
|
||||||
|
sources: [{ id: "user", type: "user", label: "User", fieldRequired: true }],
|
||||||
|
editable: "user",
|
||||||
|
required: true,
|
||||||
|
placeholder: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rescheduleReason",
|
||||||
|
type: "textarea",
|
||||||
|
views: [{ id: "reschedule", label: "Reschedule View" }],
|
||||||
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
||||||
|
editable: "system-but-optional",
|
||||||
|
required: false,
|
||||||
|
defaultLabel: "reason_for_reschedule",
|
||||||
|
defaultPlaceholder: "reschedule_placeholder",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
locations: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
phoneOnlyEventTypeId = phoneOnlyEventType.id;
|
||||||
|
|
||||||
const team2EventType = await eventTypesRepositoryFixture.createTeamEventType({
|
const team2EventType = await eventTypesRepositoryFixture.createTeamEventType({
|
||||||
schedulingType: "COLLECTIVE",
|
schedulingType: "COLLECTIVE",
|
||||||
team: {
|
team: {
|
||||||
connect: { id: team2.id },
|
connect: { id: team2.id },
|
||||||
},
|
},
|
||||||
title: "Collective Event Type 2",
|
title: `team-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: "collective-event-type-2",
|
slug: team2EventTypeSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
assignAllTeamMembers: true,
|
assignAllTeamMembers: true,
|
||||||
bookingFields: [],
|
bookingFields: [],
|
||||||
@@ -322,6 +407,60 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should create a phone based booking", async () => {
|
||||||
|
const body: CreateBookingInput_2024_08_13 = {
|
||||||
|
start: new Date(Date.UTC(2030, 0, 8, 15, 0, 0)).toISOString(),
|
||||||
|
eventTypeId: phoneOnlyEventTypeId,
|
||||||
|
attendee: {
|
||||||
|
name: "alice",
|
||||||
|
phoneNumber: "+919876543210",
|
||||||
|
timeZone: "Europe/Madrid",
|
||||||
|
language: "es",
|
||||||
|
},
|
||||||
|
meetingUrl: "https://meet.google.com/abc-def-ghi",
|
||||||
|
};
|
||||||
|
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.post("/v2/bookings")
|
||||||
|
.send(body)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.expect(201)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: CreateBookingOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
expect(responseDataIsBooking(responseBody.data)).toBe(true);
|
||||||
|
|
||||||
|
if (responseDataIsBooking(responseBody.data)) {
|
||||||
|
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||||
|
expect(data.id).toBeDefined();
|
||||||
|
expect(data.uid).toBeDefined();
|
||||||
|
expect(data.hosts.length).toEqual(1);
|
||||||
|
expect(data.hosts[0].id).toEqual(teamUser.id);
|
||||||
|
expect(data.status).toEqual("accepted");
|
||||||
|
expect(data.start).toEqual(body.start);
|
||||||
|
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 15, 15, 0)).toISOString());
|
||||||
|
expect(data.duration).toEqual(15);
|
||||||
|
expect(data.eventTypeId).toEqual(phoneOnlyEventTypeId);
|
||||||
|
expect(data.attendees.length).toEqual(1);
|
||||||
|
expect(data.attendees[0]).toEqual({
|
||||||
|
name: body.attendee.name,
|
||||||
|
email: "919876543210@sms.cal.com",
|
||||||
|
phoneNumber: body.attendee.phoneNumber,
|
||||||
|
timeZone: body.attendee.timeZone,
|
||||||
|
language: body.attendee.language,
|
||||||
|
absent: false,
|
||||||
|
});
|
||||||
|
expect(data.meetingUrl).toEqual(body.meetingUrl);
|
||||||
|
expect(data.absentHost).toEqual(false);
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
"Invalid response data - expected booking but received array of possibily recurring bookings"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should create a team 2 booking", async () => {
|
it("should create a team 2 booking", async () => {
|
||||||
const body: CreateBookingInput_2024_08_13 = {
|
const body: CreateBookingInput_2024_08_13 = {
|
||||||
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
|
start: new Date(Date.UTC(2030, 0, 8, 10, 0, 0)).toISOString(),
|
||||||
@@ -398,7 +537,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
| RecurringBookingOutput_2024_08_13
|
| RecurringBookingOutput_2024_08_13
|
||||||
| GetSeatedBookingOutput_2024_08_13
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
)[] = responseBody.data;
|
)[] = responseBody.data;
|
||||||
expect(data.length).toEqual(1);
|
expect(data.length).toEqual(2);
|
||||||
expect(data[0].eventTypeId).toEqual(team1EventTypeId);
|
expect(data[0].eventTypeId).toEqual(team1EventTypeId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -422,6 +561,43 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should get bookings by teamId and eventTypeId", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/bookings?teamId=${team2.id}&eventTypeId=${team2EventTypeId}`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(1);
|
||||||
|
expect(data[0].eventTypeId).toEqual(team2EventTypeId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not get bookings by teamId and non existing eventTypeId", async () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get(`/v2/bookings?teamId=${team2.id}&eventTypeId=90909`)
|
||||||
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
|
.expect(200)
|
||||||
|
.then(async (response) => {
|
||||||
|
const responseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||||
|
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||||
|
expect(responseBody.data).toBeDefined();
|
||||||
|
const data: (
|
||||||
|
| BookingOutput_2024_08_13
|
||||||
|
| RecurringBookingOutput_2024_08_13
|
||||||
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
|
)[] = responseBody.data;
|
||||||
|
expect(data.length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("should should get bookings by teamIds", async () => {
|
it("should should get bookings by teamIds", async () => {
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
.get(`/v2/bookings?teamIds=${team1.id},${team2.id}`)
|
.get(`/v2/bookings?teamIds=${team1.id},${team2.id}`)
|
||||||
@@ -436,7 +612,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
| RecurringBookingOutput_2024_08_13
|
| RecurringBookingOutput_2024_08_13
|
||||||
| GetSeatedBookingOutput_2024_08_13
|
| GetSeatedBookingOutput_2024_08_13
|
||||||
)[] = responseBody.data;
|
)[] = responseBody.data;
|
||||||
expect(data.length).toEqual(2);
|
expect(data.length).toEqual(3);
|
||||||
expect(data.find((booking) => booking.eventTypeId === team1EventTypeId)).toBeDefined();
|
expect(data.find((booking) => booking.eventTypeId === team1EventTypeId)).toBeDefined();
|
||||||
expect(data.find((booking) => booking.eventTypeId === team2EventTypeId)).toBeDefined();
|
expect(data.find((booking) => booking.eventTypeId === team2EventTypeId)).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
|
|||||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -58,12 +59,13 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
let oAuthClient: PlatformOAuthClient;
|
let oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "bookings-controller-e2e@api.com";
|
const userEmail = `user-bookings-user-${randomString()}@api.com`;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
let eventTypeId: number;
|
let eventTypeId: number;
|
||||||
const eventTypeSlug = "peer-coding";
|
const eventTypeSlug = `user-bookings-event-type-${randomString()}`;
|
||||||
let recurringEventTypeId: number;
|
let recurringEventTypeId: number;
|
||||||
|
const recurringEventTypeSlug = `user-bookings-event-type-${randomString()}`;
|
||||||
|
|
||||||
let createdBooking: BookingOutput_2024_08_13;
|
let createdBooking: BookingOutput_2024_08_13;
|
||||||
let rescheduledBooking: BookingOutput_2024_08_13;
|
let rescheduledBooking: BookingOutput_2024_08_13;
|
||||||
@@ -91,12 +93,14 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
email: userEmail,
|
email: userEmail,
|
||||||
username: `bob-${Math.floor(Math.random() * 1000)}@gmail.com`,
|
username: userEmail,
|
||||||
platformOAuthClients: {
|
platformOAuthClients: {
|
||||||
connect: {
|
connect: {
|
||||||
id: oAuthClient.id,
|
id: oAuthClient.id,
|
||||||
@@ -105,13 +109,18 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `user-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
await schedulesService.createUserSchedule(user.id, userSchedule);
|
await schedulesService.createUserSchedule(user.id, userSchedule);
|
||||||
|
|
||||||
const event = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
eventTypeId = event.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
|
// note(Lauris): freq 2 means weekly, interval 1 means every week and count 3 means 3 weeks in a row
|
||||||
{
|
{
|
||||||
title: "peer coding recurring",
|
title: "peer coding recurring",
|
||||||
slug: "peer-coding-recurring",
|
slug: recurringEventTypeSlug,
|
||||||
length: 60,
|
length: 60,
|
||||||
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
recurringEvent: { freq: 2, count: 3, interval: 1 },
|
||||||
},
|
},
|
||||||
@@ -158,6 +167,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
rating: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
app = moduleRef.createNestApplication();
|
app = moduleRef.createNestApplication();
|
||||||
@@ -332,6 +342,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
userId: "100",
|
userId: "100",
|
||||||
},
|
},
|
||||||
|
guests: ["bob@gmail.com"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const beforeCreate = new Date();
|
const beforeCreate = new Date();
|
||||||
@@ -353,6 +364,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.uid).toBeDefined();
|
expect(data.uid).toBeDefined();
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
expect(data.hosts[0].id).toEqual(user.id);
|
||||||
expect(data.hosts[0].username).toEqual(user.username);
|
expect(data.hosts[0].username).toEqual(user.username);
|
||||||
|
expect(data.hosts[0].email).toEqual(user.email);
|
||||||
expect(data.status).toEqual("accepted");
|
expect(data.status).toEqual("accepted");
|
||||||
expect(data.start).toEqual(body.start);
|
expect(data.start).toEqual(body.start);
|
||||||
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString());
|
expect(data.end).toEqual(new Date(Date.UTC(2030, 0, 8, 14, 0, 0)).toISOString());
|
||||||
@@ -376,13 +388,21 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
name: body.attendee.name,
|
name: body.attendee.name,
|
||||||
email: body.attendee.email,
|
email: body.attendee.email,
|
||||||
...body.bookingFieldsResponses,
|
...body.bookingFieldsResponses,
|
||||||
|
guests: body.guests,
|
||||||
});
|
});
|
||||||
|
expect(data.guests).toEqual(body.guests);
|
||||||
|
|
||||||
// Check createdAt date is between the time of the request and after the request
|
// Check createdAt date is between the time of the request and after the request
|
||||||
const createdAtDate = new Date(data.createdAt);
|
const createdAtDate = new Date(data.createdAt);
|
||||||
expect(createdAtDate.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
expect(createdAtDate.getTime()).toBeGreaterThanOrEqual(beforeCreate.getTime());
|
||||||
expect(createdAtDate.getTime()).toBeLessThanOrEqual(afterCreate.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);
|
expect(data.metadata).toEqual(body.metadata);
|
||||||
createdBooking = data;
|
createdBooking = data;
|
||||||
} else {
|
} else {
|
||||||
@@ -515,6 +535,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.uid).toEqual(createdBooking.uid);
|
expect(data.uid).toEqual(createdBooking.uid);
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
expect(data.hosts[0].id).toEqual(user.id);
|
||||||
expect(data.hosts[0].username).toEqual(user.username);
|
expect(data.hosts[0].username).toEqual(user.username);
|
||||||
|
expect(data.hosts[0].email).toEqual(user.email);
|
||||||
expect(data.status).toEqual(createdBooking.status);
|
expect(data.status).toEqual(createdBooking.status);
|
||||||
expect(data.start).toEqual(createdBooking.start);
|
expect(data.start).toEqual(createdBooking.start);
|
||||||
expect(data.end).toEqual(createdBooking.end);
|
expect(data.end).toEqual(createdBooking.end);
|
||||||
@@ -532,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 () => {
|
it("should should get 1 recurrence of a recurring booking", async () => {
|
||||||
const recurrenceUid = createdRecurringBooking[0].uid;
|
const recurrenceUid = createdRecurringBooking[0].uid;
|
||||||
return request(app.getHttpServer())
|
return request(app.getHttpServer())
|
||||||
@@ -549,6 +594,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toEqual(createdRecurringBooking[0].id);
|
expect(data.id).toEqual(createdRecurringBooking[0].id);
|
||||||
expect(data.uid).toEqual(createdRecurringBooking[0].uid);
|
expect(data.uid).toEqual(createdRecurringBooking[0].uid);
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual(createdRecurringBooking[0].status);
|
||||||
expect(data.start).toEqual(createdRecurringBooking[0].start);
|
expect(data.start).toEqual(createdRecurringBooking[0].start);
|
||||||
expect(data.end).toEqual(createdRecurringBooking[0].end);
|
expect(data.end).toEqual(createdRecurringBooking[0].end);
|
||||||
@@ -853,7 +900,63 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
|
|
||||||
it("should should get bookings by before specified end time", async () => {
|
it("should should get bookings by before specified end time", async () => {
|
||||||
return request(app.getHttpServer())
|
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)
|
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
@@ -988,6 +1091,46 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data[1].start).toEqual(createdBooking.start);
|
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", () => {
|
describe("reschedule bookings", () => {
|
||||||
@@ -1018,6 +1161,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toBeDefined();
|
expect(data.id).toBeDefined();
|
||||||
expect(data.uid).toBeDefined();
|
expect(data.uid).toBeDefined();
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual(createdBooking.status);
|
||||||
expect(data.duration).toEqual(createdBooking.duration);
|
expect(data.duration).toEqual(createdBooking.duration);
|
||||||
expect(data.eventTypeId).toEqual(createdBooking.eventTypeId);
|
expect(data.eventTypeId).toEqual(createdBooking.eventTypeId);
|
||||||
@@ -1075,6 +1220,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toBeDefined();
|
expect(data.id).toBeDefined();
|
||||||
expect(data.uid).toBeDefined();
|
expect(data.uid).toBeDefined();
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual(createdRecurringBooking[0].status);
|
||||||
expect(data.start).toEqual(body.start);
|
expect(data.start).toEqual(body.start);
|
||||||
expect(data.end).toEqual(new Date(Date.UTC(2035, 0, 9, 15, 0, 0)).toISOString());
|
expect(data.end).toEqual(new Date(Date.UTC(2035, 0, 9, 15, 0, 0)).toISOString());
|
||||||
@@ -1155,6 +1302,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toEqual(booking.id);
|
expect(data.id).toEqual(booking.id);
|
||||||
expect(data.uid).toEqual(booking.uid);
|
expect(data.uid).toEqual(booking.uid);
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual(booking.status);
|
||||||
expect(data.start).toEqual(booking.start);
|
expect(data.start).toEqual(booking.start);
|
||||||
expect(data.end).toEqual(booking.end);
|
expect(data.end).toEqual(booking.end);
|
||||||
@@ -1187,6 +1336,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toEqual(booking.id);
|
expect(data.id).toEqual(booking.id);
|
||||||
expect(data.uid).toEqual(booking.uid);
|
expect(data.uid).toEqual(booking.uid);
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual(booking.status);
|
||||||
expect(data.start).toEqual(booking.start);
|
expect(data.start).toEqual(booking.start);
|
||||||
expect(data.end).toEqual(booking.end);
|
expect(data.end).toEqual(booking.end);
|
||||||
@@ -1225,6 +1376,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
expect(data.id).toBeDefined();
|
expect(data.id).toBeDefined();
|
||||||
expect(data.uid).toBeDefined();
|
expect(data.uid).toBeDefined();
|
||||||
expect(data.hosts[0].id).toEqual(user.id);
|
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.status).toEqual("cancelled");
|
||||||
expect(data.cancellationReason).toEqual(body.cancellationReason);
|
expect(data.cancellationReason).toEqual(body.cancellationReason);
|
||||||
expect(data.start).toEqual(rescheduledBooking.start);
|
expect(data.start).toEqual(rescheduledBooking.start);
|
||||||
@@ -1282,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 {
|
function responseDataIsRecurranceBooking(data: any): data is RecurringBookingOutput_2024_08_13 {
|
||||||
return (
|
return (
|
||||||
!Array.isArray(data) &&
|
!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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
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 oAuthClient: PlatformOAuthClient;
|
||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
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 user: User;
|
||||||
|
|
||||||
let variableLengthEventType: EventType;
|
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;
|
let normalEventType: EventType;
|
||||||
|
const NORMAL_EVENT_TYPE_SLUG = `variable-length-bookings-2024-08-13-event-type-${randomString()}`;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
const moduleRef = await withApiAuth(
|
const moduleRef = await withApiAuth(
|
||||||
@@ -63,7 +65,9 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
schedulesService = moduleRef.get<SchedulesService_2024_04_15>(SchedulesService_2024_04_15);
|
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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
|
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
@@ -76,7 +80,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
const userSchedule: CreateScheduleInput_2024_04_15 = {
|
||||||
name: "working time",
|
name: `variable-length-bookings-2024-08-13-schedule-${randomString()}`,
|
||||||
timeZone: "Europe/Rome",
|
timeZone: "Europe/Rome",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
};
|
};
|
||||||
@@ -84,8 +88,8 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
|
|
||||||
variableLengthEventType = await eventTypesRepositoryFixture.create(
|
variableLengthEventType = await eventTypesRepositoryFixture.create(
|
||||||
{
|
{
|
||||||
title: "variable length event",
|
title: `variable-length-bookings-2024-08-13-event-type-${randomString()}`,
|
||||||
slug: variableLengthEventTypeSlug,
|
slug: VARIABLE_LENGTH_EVENT_TYPE_SLUG,
|
||||||
length: 15,
|
length: 15,
|
||||||
metadata: { multipleDuration: [15, 30, 60] },
|
metadata: { multipleDuration: [15, 30, 60] },
|
||||||
},
|
},
|
||||||
@@ -93,7 +97,11 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
normalEventType = await eventTypesRepositoryFixture.create(
|
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
|
user.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { Request } from "express";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
handleNewBooking,
|
|
||||||
handleNewRecurringBooking,
|
handleNewRecurringBooking,
|
||||||
getAllUserBookings,
|
getAllUserBookings,
|
||||||
handleInstantMeeting,
|
handleInstantMeeting,
|
||||||
@@ -23,6 +22,7 @@ import {
|
|||||||
handleMarkNoShow,
|
handleMarkNoShow,
|
||||||
confirmBookingHandler,
|
confirmBookingHandler,
|
||||||
} from "@calcom/platform-libraries";
|
} from "@calcom/platform-libraries";
|
||||||
|
import { handleNewBooking } from "@calcom/platform-libraries";
|
||||||
import {
|
import {
|
||||||
CreateBookingInput_2024_08_13,
|
CreateBookingInput_2024_08_13,
|
||||||
CreateBookingInput,
|
CreateBookingInput,
|
||||||
@@ -340,7 +340,10 @@ export class BookingsService_2024_08_13 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bookingRequest = await this.inputService.createCancelBookingRequest(request, bookingUid, body);
|
const bookingRequest = await this.inputService.createCancelBookingRequest(request, bookingUid, body);
|
||||||
await handleCancelBooking(bookingRequest);
|
const res = await handleCancelBooking(bookingRequest);
|
||||||
|
if (!res.onlyRemovedAttendee) {
|
||||||
|
await this.billingService.cancelUsageByBookingUid(res.bookingUid);
|
||||||
|
}
|
||||||
|
|
||||||
if ("cancelSubsequentBookings" in body && body.cancelSubsequentBookings) {
|
if ("cancelSubsequentBookings" in body && body.cancelSubsequentBookings) {
|
||||||
return this.getAllRecurringBookingsByIndividualUid(bookingUid);
|
return this.getAllRecurringBookingsByIndividualUid(bookingUid);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { OAuthFlowService } from "@/modules/oauth-clients/services/oauth-flow.se
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||||
import { Logger } from "@nestjs/common";
|
import { Logger } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { CreationSource } from "@prisma/client";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import { NextApiRequest } from "next/types";
|
import { NextApiRequest } from "next/types";
|
||||||
@@ -97,10 +98,14 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
|
|
||||||
if (oAuthClientParams) {
|
if (oAuthClientParams) {
|
||||||
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
||||||
newRequest.body = { ...bodyTransformed, noEmail: !oAuthClientParams.arePlatformEmailsEnabled };
|
newRequest.body = {
|
||||||
|
...bodyTransformed,
|
||||||
|
noEmail: !oAuthClientParams.arePlatformEmailsEnabled,
|
||||||
|
creationSource: CreationSource.API_V2,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
Object.assign(newRequest, { userId, platformBookingLocation: location });
|
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;
|
return newRequest as unknown as BookingRequest;
|
||||||
@@ -146,6 +151,8 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
);
|
);
|
||||||
const endTime = startTime.plus({ minutes: lengthInMinutes });
|
const endTime = startTime.plus({ minutes: lengthInMinutes });
|
||||||
|
|
||||||
|
const guests = inputBooking.guests;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: startTime.toISO(),
|
start: startTime.toISO(),
|
||||||
end: endTime.toISO(),
|
end: endTime.toISO(),
|
||||||
@@ -154,15 +161,22 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
language: inputBooking.attendee.language || "en",
|
language: inputBooking.attendee.language || "en",
|
||||||
metadata: inputBooking.metadata || {},
|
metadata: inputBooking.metadata || {},
|
||||||
hasHashedBookingLink: false,
|
hasHashedBookingLink: false,
|
||||||
guests: inputBooking.guests,
|
guests,
|
||||||
// note(Lauris): responses with name and email are required by the handleNewBooking
|
// note(Lauris): responses with name and email are required by the handleNewBooking
|
||||||
responses: inputBooking.bookingFieldsResponses
|
responses: inputBooking.bookingFieldsResponses
|
||||||
? {
|
? {
|
||||||
...inputBooking.bookingFieldsResponses,
|
...inputBooking.bookingFieldsResponses,
|
||||||
name: inputBooking.attendee.name,
|
name: inputBooking.attendee.name,
|
||||||
email: inputBooking.attendee.email,
|
email: inputBooking.attendee.email ?? "",
|
||||||
|
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
|
||||||
|
guests,
|
||||||
}
|
}
|
||||||
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email },
|
: {
|
||||||
|
name: inputBooking.attendee.name,
|
||||||
|
email: inputBooking.attendee.email ?? "",
|
||||||
|
attendeePhoneNumber: inputBooking.attendee.phoneNumber,
|
||||||
|
guests,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +225,7 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
|
|
||||||
newRequest.body = bodyTransformed.map((event) => ({
|
newRequest.body = bodyTransformed.map((event) => ({
|
||||||
...event,
|
...event,
|
||||||
|
creationSource: CreationSource.API_V2,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return newRequest as unknown as BookingRequest;
|
return newRequest as unknown as BookingRequest;
|
||||||
@@ -246,6 +261,8 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
inputBooking.attendee.timeZone
|
inputBooking.attendee.timeZone
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const guests = inputBooking.guests;
|
||||||
|
|
||||||
for (let i = 0; i < repeatsTimes; i++) {
|
for (let i = 0; i < repeatsTimes; i++) {
|
||||||
const endTime = startTime.plus({ minutes: eventType.length });
|
const endTime = startTime.plus({ minutes: eventType.length });
|
||||||
|
|
||||||
@@ -258,15 +275,16 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
language: inputBooking.attendee.language || "en",
|
language: inputBooking.attendee.language || "en",
|
||||||
metadata: inputBooking.metadata || {},
|
metadata: inputBooking.metadata || {},
|
||||||
hasHashedBookingLink: false,
|
hasHashedBookingLink: false,
|
||||||
guests: inputBooking.guests,
|
guests,
|
||||||
// note(Lauris): responses with name and email are required by the handleNewBooking
|
// note(Lauris): responses with name and email are required by the handleNewBooking
|
||||||
responses: inputBooking.bookingFieldsResponses
|
responses: inputBooking.bookingFieldsResponses
|
||||||
? {
|
? {
|
||||||
...inputBooking.bookingFieldsResponses,
|
...inputBooking.bookingFieldsResponses,
|
||||||
name: inputBooking.attendee.name,
|
name: inputBooking.attendee.name,
|
||||||
email: inputBooking.attendee.email,
|
email: inputBooking.attendee.email,
|
||||||
|
guests,
|
||||||
}
|
}
|
||||||
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email },
|
: { name: inputBooking.attendee.name, email: inputBooking.attendee.email, guests },
|
||||||
schedulingType: eventType.schedulingType,
|
schedulingType: eventType.schedulingType,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -305,10 +323,14 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
const location = await this.getRescheduleBookingLocation(bookingUid);
|
const location = await this.getRescheduleBookingLocation(bookingUid);
|
||||||
if (oAuthClientParams) {
|
if (oAuthClientParams) {
|
||||||
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
Object.assign(newRequest, { userId, ...oAuthClientParams, platformBookingLocation: location });
|
||||||
newRequest.body = { ...bodyTransformed, noEmail: !oAuthClientParams.arePlatformEmailsEnabled };
|
newRequest.body = {
|
||||||
|
...bodyTransformed,
|
||||||
|
noEmail: !oAuthClientParams.arePlatformEmailsEnabled,
|
||||||
|
creationSource: CreationSource.API_V2,
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
Object.assign(newRequest, { userId, platformBookingLocation: location });
|
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;
|
return newRequest as unknown as BookingRequest;
|
||||||
@@ -441,11 +463,18 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
teamIds: queryParams.teamsIds || (queryParams.teamId ? [queryParams.teamId] : undefined),
|
teamIds: queryParams.teamsIds || (queryParams.teamId ? [queryParams.teamId] : undefined),
|
||||||
eventTypeIds:
|
eventTypeIds:
|
||||||
queryParams.eventTypeIds || (queryParams.eventTypeId ? [queryParams.eventTypeId] : undefined),
|
queryParams.eventTypeIds || (queryParams.eventTypeId ? [queryParams.eventTypeId] : undefined),
|
||||||
|
afterUpdatedDate: queryParams.afterUpdatedAt,
|
||||||
|
beforeUpdatedDate: queryParams.beforeUpdatedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
transformGetBookingsSort(queryParams: GetBookingsInput_2024_08_13) {
|
transformGetBookingsSort(queryParams: GetBookingsInput_2024_08_13) {
|
||||||
if (!queryParams.sortStart && !queryParams.sortEnd && !queryParams.sortCreated) {
|
if (
|
||||||
|
!queryParams.sortStart &&
|
||||||
|
!queryParams.sortEnd &&
|
||||||
|
!queryParams.sortCreated &&
|
||||||
|
!queryParams.sortUpdatedAt
|
||||||
|
) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,6 +482,7 @@ export class InputBookingsService_2024_08_13 {
|
|||||||
sortStart: queryParams.sortStart,
|
sortStart: queryParams.sortStart,
|
||||||
sortEnd: queryParams.sortEnd,
|
sortEnd: queryParams.sortEnd,
|
||||||
sortCreated: queryParams.sortCreated,
|
sortCreated: queryParams.sortCreated,
|
||||||
|
sortUpdated: queryParams.sortUpdatedAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ type DatabaseBooking = Booking & {
|
|||||||
email: string;
|
email: string;
|
||||||
timeZone: string;
|
timeZone: string;
|
||||||
locale: string | null;
|
locale: string | null;
|
||||||
|
phoneNumber?: string | null;
|
||||||
noShow: boolean | null;
|
noShow: boolean | null;
|
||||||
bookingSeat?: BookingSeat | null;
|
bookingSeat?: BookingSeat | null;
|
||||||
}[];
|
}[];
|
||||||
@@ -107,7 +108,9 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
hosts: [this.getHost(databaseBooking.user)],
|
hosts: [this.getHost(databaseBooking.user)],
|
||||||
status: databaseBooking.status.toLowerCase(),
|
status: databaseBooking.status.toLowerCase(),
|
||||||
cancellationReason: databaseBooking.cancellationReason || undefined,
|
cancellationReason: databaseBooking.cancellationReason || undefined,
|
||||||
|
cancelledByEmail: databaseBooking.cancelledBy || undefined,
|
||||||
reschedulingReason: bookingResponses?.rescheduledReason,
|
reschedulingReason: bookingResponses?.rescheduledReason,
|
||||||
|
rescheduledByEmail: databaseBooking.rescheduledBy || undefined,
|
||||||
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
||||||
start: databaseBooking.startTime,
|
start: databaseBooking.startTime,
|
||||||
end: databaseBooking.endTime,
|
end: databaseBooking.endTime,
|
||||||
@@ -121,6 +124,7 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
timeZone: attendee.timeZone,
|
timeZone: attendee.timeZone,
|
||||||
language: attendee.locale,
|
language: attendee.locale,
|
||||||
absent: !!attendee.noShow,
|
absent: !!attendee.noShow,
|
||||||
|
phoneNumber: attendee.phoneNumber ?? undefined,
|
||||||
})),
|
})),
|
||||||
guests: bookingResponses.guests,
|
guests: bookingResponses.guests,
|
||||||
location,
|
location,
|
||||||
@@ -128,6 +132,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
meetingUrl: location,
|
meetingUrl: location,
|
||||||
absentHost: !!databaseBooking.noShowHost,
|
absentHost: !!databaseBooking.noShowHost,
|
||||||
createdAt: databaseBooking.createdAt,
|
createdAt: databaseBooking.createdAt,
|
||||||
|
updatedAt: databaseBooking.updatedAt,
|
||||||
|
rating: databaseBooking.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
const bookingTransformed = plainToClass(BookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
const bookingTransformed = plainToClass(BookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
||||||
@@ -196,7 +202,9 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
hosts: [this.getHost(databaseBooking.user)],
|
hosts: [this.getHost(databaseBooking.user)],
|
||||||
status: databaseBooking.status.toLowerCase(),
|
status: databaseBooking.status.toLowerCase(),
|
||||||
cancellationReason: databaseBooking.cancellationReason || undefined,
|
cancellationReason: databaseBooking.cancellationReason || undefined,
|
||||||
|
cancelledByEmail: databaseBooking.cancelledBy || undefined,
|
||||||
reschedulingReason: bookingResponses?.rescheduledReason,
|
reschedulingReason: bookingResponses?.rescheduledReason,
|
||||||
|
rescheduledByEmail: databaseBooking.rescheduledBy || undefined,
|
||||||
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
rescheduledFromUid: databaseBooking.fromReschedule || undefined,
|
||||||
start: databaseBooking.startTime,
|
start: databaseBooking.startTime,
|
||||||
end: databaseBooking.endTime,
|
end: databaseBooking.endTime,
|
||||||
@@ -219,6 +227,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
absentHost: !!databaseBooking.noShowHost,
|
absentHost: !!databaseBooking.noShowHost,
|
||||||
bookingFieldsResponses: databaseBooking.responses,
|
bookingFieldsResponses: databaseBooking.responses,
|
||||||
createdAt: databaseBooking.createdAt,
|
createdAt: databaseBooking.createdAt,
|
||||||
|
updatedAt: databaseBooking.updatedAt,
|
||||||
|
rating: databaseBooking.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
const bookingTransformed = plainToClass(RecurringBookingOutput_2024_08_13, booking, {
|
const bookingTransformed = plainToClass(RecurringBookingOutput_2024_08_13, booking, {
|
||||||
@@ -265,6 +275,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
meetingUrl: location,
|
meetingUrl: location,
|
||||||
absentHost: !!databaseBooking.noShowHost,
|
absentHost: !!databaseBooking.noShowHost,
|
||||||
createdAt: databaseBooking.createdAt,
|
createdAt: databaseBooking.createdAt,
|
||||||
|
updatedAt: databaseBooking.updatedAt,
|
||||||
|
rating: databaseBooking.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = plainToClass(GetSeatedBookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
const parsed = plainToClass(GetSeatedBookingOutput_2024_08_13, booking, { strategy: "excludeAll" });
|
||||||
@@ -274,7 +286,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
const { responses } = safeParse(
|
const { responses } = safeParse(
|
||||||
seatedBookingDataSchema,
|
seatedBookingDataSchema,
|
||||||
attendee.bookingSeat?.data,
|
attendee.bookingSeat?.data,
|
||||||
defaultSeatedBookingData
|
defaultSeatedBookingData,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const attendeeData = {
|
const attendeeData = {
|
||||||
@@ -291,7 +304,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
attendeeParsed.metadata = safeParse(
|
attendeeParsed.metadata = safeParse(
|
||||||
seatedBookingMetadataSchema,
|
seatedBookingMetadataSchema,
|
||||||
attendee.bookingSeat?.metadata,
|
attendee.bookingSeat?.metadata,
|
||||||
defaultSeatedBookingMetadata
|
defaultSeatedBookingMetadata,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
// note(Lauris): as of now email is not returned for privacy
|
// note(Lauris): as of now email is not returned for privacy
|
||||||
delete attendeeParsed.bookingFieldsResponses.email;
|
delete attendeeParsed.bookingFieldsResponses.email;
|
||||||
@@ -370,6 +384,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
recurringBookingUid: databaseBooking.recurringEventId,
|
recurringBookingUid: databaseBooking.recurringEventId,
|
||||||
absentHost: !!databaseBooking.noShowHost,
|
absentHost: !!databaseBooking.noShowHost,
|
||||||
createdAt: databaseBooking.createdAt,
|
createdAt: databaseBooking.createdAt,
|
||||||
|
updatedAt: databaseBooking.updatedAt,
|
||||||
|
rating: databaseBooking.rating,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsed = plainToClass(GetRecurringSeatedBookingOutput_2024_08_13, booking, {
|
const parsed = plainToClass(GetRecurringSeatedBookingOutput_2024_08_13, booking, {
|
||||||
@@ -381,7 +397,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
const { responses } = safeParse(
|
const { responses } = safeParse(
|
||||||
seatedBookingDataSchema,
|
seatedBookingDataSchema,
|
||||||
attendee.bookingSeat?.data,
|
attendee.bookingSeat?.data,
|
||||||
defaultSeatedBookingData
|
defaultSeatedBookingData,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
const attendeeData = {
|
const attendeeData = {
|
||||||
@@ -398,7 +415,8 @@ export class OutputBookingsService_2024_08_13 {
|
|||||||
attendeeParsed.metadata = safeParse(
|
attendeeParsed.metadata = safeParse(
|
||||||
seatedBookingMetadataSchema,
|
seatedBookingMetadataSchema,
|
||||||
attendee.bookingSeat?.metadata,
|
attendee.bookingSeat?.metadata,
|
||||||
defaultSeatedBookingMetadata
|
defaultSeatedBookingMetadata,
|
||||||
|
false
|
||||||
);
|
);
|
||||||
// note(Lauris): as of now email is not returned for privacy
|
// note(Lauris): as of now email is not returned for privacy
|
||||||
delete attendeeParsed.bookingFieldsResponses.email;
|
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 { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||||
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-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 { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||||
import {
|
import {
|
||||||
@@ -71,9 +72,12 @@ describe("Platform Calendars Endpoints", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
|
tokensRepositoryFixture = new TokensRepositoryFixture(moduleRef);
|
||||||
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
|
credentialsRepositoryFixture = new CredentialsRepositoryFixture(moduleRef);
|
||||||
organization = await teamRepositoryFixture.create({ name: "organization" });
|
organization = await teamRepositoryFixture.create({ name: `calendars-organization-${randomString()}` });
|
||||||
oAuthClient = await createOAuthClient(organization.id);
|
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);
|
const tokens = await tokensRepositoryFixture.createTokens(user.id, oAuthClient.id);
|
||||||
accessTokenSecret = tokens.accessToken;
|
accessTokenSecret = tokens.accessToken;
|
||||||
refreshTokenSecret = tokens.refreshToken;
|
refreshTokenSecret = tokens.refreshToken;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import {
|
|||||||
Post,
|
Post,
|
||||||
Body,
|
Body,
|
||||||
} from "@nestjs/common";
|
} 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 { User } from "@prisma/client";
|
||||||
import { plainToClass } from "class-transformer";
|
import { plainToClass } from "class-transformer";
|
||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
@@ -86,18 +86,16 @@ export class CalendarsController {
|
|||||||
|
|
||||||
@UseGuards(ApiAuthGuard)
|
@UseGuards(ApiAuthGuard)
|
||||||
@Get("/busy-times")
|
@Get("/busy-times")
|
||||||
@ApiOperation({ summary: "Get busy times" })
|
@ApiOperation({
|
||||||
|
summary: "Get busy times",
|
||||||
|
description:
|
||||||
|
"Get busy times from a calendar. Example request URL is `https://api.cal.com/v2/calendars/busy-times?loggedInUsersTz=Europe%2FMadrid&dateFrom=2024-12-18&dateTo=2024-12-18&calendarsToLoad[0][credentialId]=135&calendarsToLoad[0][externalId]=skrauciz%40gmail.com`",
|
||||||
|
})
|
||||||
async getBusyTimes(
|
async getBusyTimes(
|
||||||
@Query() queryParams: CalendarBusyTimesInput,
|
@Query() queryParams: CalendarBusyTimesInput,
|
||||||
@GetUser() user: UserWithProfile
|
@GetUser() user: UserWithProfile
|
||||||
): Promise<GetBusyTimesOutput> {
|
): Promise<GetBusyTimesOutput> {
|
||||||
const { loggedInUsersTz, dateFrom, dateTo, calendarsToLoad } = queryParams;
|
const { loggedInUsersTz, dateFrom, dateTo, calendarsToLoad } = queryParams;
|
||||||
if (!dateFrom || !dateTo) {
|
|
||||||
return {
|
|
||||||
status: SUCCESS_STATUS,
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const busyTimes = await this.calendarsService.getBusyTimes(
|
const busyTimes = await this.calendarsService.getBusyTimes(
|
||||||
calendarsToLoad,
|
calendarsToLoad,
|
||||||
@@ -125,10 +123,15 @@ export class CalendarsController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiParam({
|
||||||
|
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
|
||||||
|
type: String,
|
||||||
|
name: "calendar",
|
||||||
|
})
|
||||||
@UseGuards(ApiAuthGuard)
|
@UseGuards(ApiAuthGuard)
|
||||||
@Get("/:calendar/connect")
|
@Get("/:calendar/connect")
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ApiOperation({ summary: "Get connect URL" })
|
@ApiOperation({ summary: "Get oAuth connect URL" })
|
||||||
async redirect(
|
async redirect(
|
||||||
@Req() req: Request,
|
@Req() req: Request,
|
||||||
@Headers("Authorization") authorization: string,
|
@Headers("Authorization") authorization: string,
|
||||||
@@ -148,10 +151,15 @@ export class CalendarsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiParam({
|
||||||
|
enum: [OFFICE_365_CALENDAR, GOOGLE_CALENDAR],
|
||||||
|
type: String,
|
||||||
|
name: "calendar",
|
||||||
|
})
|
||||||
@Get("/:calendar/save")
|
@Get("/:calendar/save")
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Redirect(undefined, 301)
|
@Redirect(undefined, 301)
|
||||||
@ApiOperation({ summary: "Save a calendar" })
|
@ApiOperation({ summary: "Save an oAuth calendar credentials" })
|
||||||
async save(
|
async save(
|
||||||
@Query("state") state: string,
|
@Query("state") state: string,
|
||||||
@Query("code") code: string,
|
@Query("code") code: string,
|
||||||
@@ -179,6 +187,11 @@ export class CalendarsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiParam({
|
||||||
|
enum: [APPLE_CALENDAR],
|
||||||
|
type: String,
|
||||||
|
name: "calendar",
|
||||||
|
})
|
||||||
@UseGuards(ApiAuthGuard)
|
@UseGuards(ApiAuthGuard)
|
||||||
@Post("/:calendar/credentials")
|
@Post("/:calendar/credentials")
|
||||||
@ApiOperation({ summary: "Sync credentials" })
|
@ApiOperation({ summary: "Sync credentials" })
|
||||||
@@ -200,6 +213,11 @@ export class CalendarsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiParam({
|
||||||
|
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
|
||||||
|
type: String,
|
||||||
|
name: "calendar",
|
||||||
|
})
|
||||||
@Get("/:calendar/check")
|
@Get("/:calendar/check")
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@UseGuards(ApiAuthGuard, PermissionsGuard)
|
@UseGuards(ApiAuthGuard, PermissionsGuard)
|
||||||
@@ -221,6 +239,11 @@ export class CalendarsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiParam({
|
||||||
|
enum: [APPLE_CALENDAR, GOOGLE_CALENDAR, OFFICE_365_CALENDAR],
|
||||||
|
type: String,
|
||||||
|
name: "calendar",
|
||||||
|
})
|
||||||
@UseGuards(ApiAuthGuard)
|
@UseGuards(ApiAuthGuard)
|
||||||
@Post("/:calendar/disconnect")
|
@Post("/:calendar/disconnect")
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import { DateTime } from "luxon";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { APPS_TYPE_ID_MAPPING } from "@calcom/platform-constants";
|
import { APPS_TYPE_ID_MAPPING } from "@calcom/platform-constants";
|
||||||
import { getConnectedDestinationCalendars, getBusyCalendarTimes } from "@calcom/platform-libraries";
|
import {
|
||||||
|
getConnectedDestinationCalendarsAndEnsureDefaultsInDb,
|
||||||
|
getBusyCalendarTimes,
|
||||||
|
} from "@calcom/platform-libraries";
|
||||||
import { Calendar } from "@calcom/platform-types";
|
import { Calendar } from "@calcom/platform-types";
|
||||||
import { PrismaClient } from "@calcom/prisma";
|
import { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
@@ -41,12 +44,18 @@ export class CalendarsService {
|
|||||||
if (!userWithCalendars) {
|
if (!userWithCalendars) {
|
||||||
throw new NotFoundException("User not found");
|
throw new NotFoundException("User not found");
|
||||||
}
|
}
|
||||||
|
return getConnectedDestinationCalendarsAndEnsureDefaultsInDb({
|
||||||
return getConnectedDestinationCalendars(
|
user: {
|
||||||
userWithCalendars,
|
...userWithCalendars,
|
||||||
false,
|
allSelectedCalendars: userWithCalendars.selectedCalendars,
|
||||||
this.dbWrite.prisma as unknown as PrismaClient
|
userLevelSelectedCalendars: userWithCalendars.selectedCalendars.filter(
|
||||||
);
|
(calendar) => !calendar.eventTypeId
|
||||||
|
),
|
||||||
|
},
|
||||||
|
onboarding: false,
|
||||||
|
eventTypeId: null,
|
||||||
|
prisma: this.dbWrite.prisma as unknown as PrismaClient,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBusyTimes(
|
async getBusyTimes(
|
||||||
@@ -64,7 +73,6 @@ export class CalendarsService {
|
|||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
const calendarBusyTimes = await getBusyCalendarTimes(
|
const calendarBusyTimes = await getBusyCalendarTimes(
|
||||||
"",
|
|
||||||
credentials,
|
credentials,
|
||||||
dateFrom,
|
dateFrom,
|
||||||
dateTo,
|
dateTo,
|
||||||
|
|||||||
+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 { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||||
|
import { randomString } from "test/utils/randomString";
|
||||||
import { withApiAuth } from "test/utils/withApiAuth";
|
import { withApiAuth } from "test/utils/withApiAuth";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -77,8 +78,8 @@ describe("Event types Endpoints", () => {
|
|||||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||||
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
|
||||||
|
|
||||||
const userEmail = "event-types-test-e2e@api.com";
|
const userEmail = `event-types-2024-04-15-user-${randomString()}@api.com`;
|
||||||
const name = "bob-the-builder";
|
const name = `event-types-2024-04-15-user-${randomString()}`;
|
||||||
const username = name;
|
const username = name;
|
||||||
let eventType: EventType;
|
let eventType: EventType;
|
||||||
let user: User;
|
let user: User;
|
||||||
@@ -105,7 +106,9 @@ describe("Event types Endpoints", () => {
|
|||||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||||
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(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);
|
oAuthClient = await createOAuthClient(organization.id);
|
||||||
user = await userRepositoryFixture.create({
|
user = await userRepositoryFixture.create({
|
||||||
email: userEmail,
|
email: userEmail,
|
||||||
@@ -139,7 +142,7 @@ describe("Event types Endpoints", () => {
|
|||||||
it("should create an event type", async () => {
|
it("should create an event type", async () => {
|
||||||
const body: CreateEventTypeInput_2024_04_15 = {
|
const body: CreateEventTypeInput_2024_04_15 = {
|
||||||
title: "Test Event Type",
|
title: "Test Event Type",
|
||||||
slug: "test-event-type",
|
slug: `event-types-event-type-${randomString()}`,
|
||||||
description: "A description of the test event type.",
|
description: "A description of the test event type.",
|
||||||
length: 60,
|
length: 60,
|
||||||
hidden: false,
|
hidden: false,
|
||||||
|
|||||||
+4
-4
@@ -399,8 +399,8 @@ class PublicEventTypeOutput {
|
|||||||
workflows!: any[];
|
workflows!: any[];
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@ApiProperty()
|
@ApiPropertyOptional()
|
||||||
hosts!: any[];
|
hosts?: any[];
|
||||||
|
|
||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@Type(() => Owner)
|
@Type(() => Owner)
|
||||||
@@ -427,8 +427,8 @@ class PublicEventTypeOutput {
|
|||||||
@IsArray()
|
@IsArray()
|
||||||
@ValidateNested({ each: true })
|
@ValidateNested({ each: true })
|
||||||
@Type(() => User)
|
@Type(() => User)
|
||||||
@ApiProperty({ type: [User] })
|
@ApiPropertyOptional({ type: [User] })
|
||||||
users!: User[];
|
users?: User[];
|
||||||
|
|
||||||
@IsObject()
|
@IsObject()
|
||||||
@ApiProperty({ type: Object })
|
@ApiProperty({ type: Object })
|
||||||
|
|||||||
Loaded 100 of 1748 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user