Compare commits
773
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e481f2921 | ||
|
|
0e6203ac3c | ||
|
|
624d6d7001 | ||
|
|
ec03ab632e | ||
|
|
c81b8145b5 | ||
|
|
fa35cc5210 | ||
|
|
1eb6ff3e65 | ||
|
|
fc4ecf500a | ||
|
|
9d4522825b | ||
|
|
e91bf53d80 | ||
|
|
79b319c1dd | ||
|
|
9197a73b31 | ||
|
|
ec3656e646 | ||
|
|
ae4b807500 | ||
|
|
34a9872df1 | ||
|
|
91a480b83a | ||
|
|
db19bb3d51 | ||
|
|
9afdb1ad7e | ||
|
|
78c2a69389 | ||
|
|
5a483e75c9 | ||
|
|
bb9c4bb8f3 | ||
|
|
02fdc1361c | ||
|
|
573132198e | ||
|
|
9133bc5b16 | ||
|
|
94ae3a5a73 | ||
|
|
0757b00db7 | ||
|
|
3402f79912 | ||
|
|
56ef5d667c | ||
|
|
bb9d99c3e1 | ||
|
|
1aac14056e | ||
|
|
7af46cc7ab | ||
|
|
37cec89bf6 | ||
|
|
67843adab9 | ||
|
|
64297f027c | ||
|
|
9fe5ff7721 | ||
|
|
3076aca457 | ||
|
|
5a59bb86cd | ||
|
|
17ab088af8 | ||
|
|
b81a307bd9 | ||
|
|
b312955838 | ||
|
|
003aaaa18b | ||
|
|
3b19c064fd | ||
|
|
e26a57bf25 | ||
|
|
b5585b3974 | ||
|
|
947b05bf90 | ||
|
|
a0e5e03903 | ||
|
|
09ee39a3d8 | ||
|
|
243012e82f | ||
|
|
d96c807505 | ||
|
|
dac1069181 | ||
|
|
06d6c180d1 | ||
|
|
a2bee76da6 | ||
|
|
52ecea964d | ||
|
|
f9b294cfc2 | ||
|
|
e5f14c9316 | ||
|
|
bed76beb2f | ||
|
|
ff38d6c7db | ||
|
|
1dc20e3aec | ||
|
|
44a3a9eabb | ||
|
|
c0c52750b6 | ||
|
|
3bcaf0ec22 | ||
|
|
b881e025f6 | ||
|
|
35d140e0a7 | ||
|
|
5fd55cbfc3 | ||
|
|
18c546a171 | ||
|
|
b01ceb427d | ||
|
|
eae2711f21 | ||
|
|
fac956b761 | ||
|
|
b286244aab | ||
|
|
c46f824e3f | ||
|
|
dbe55f0804 | ||
|
|
3bcce02b92 | ||
|
|
2a23911a5b | ||
|
|
5aa24c8472 | ||
|
|
8277ce934b | ||
|
|
bb68cd73ef | ||
|
|
445b307972 | ||
|
|
d2d86a4079 | ||
|
|
7601cc2194 | ||
|
|
aa48e72c12 | ||
|
|
bec49ef417 | ||
|
|
c13aa69565 | ||
|
|
1470adb159 | ||
|
|
bf89991eef | ||
|
|
4dfab874e0 | ||
|
|
4303fb8726 | ||
|
|
64f811ed0b | ||
|
|
4d51873140 | ||
|
|
3c0fc2e88b | ||
|
|
c702da0334 | ||
|
|
92169eb0d2 | ||
|
|
3d74357e96 | ||
|
|
6593291d00 | ||
|
|
c48fb44a8d | ||
|
|
e7c908bc80 | ||
|
|
ad35e19f8e | ||
|
|
3fc8c6c84e | ||
|
|
da7aeb4a21 | ||
|
|
6197a2a5c5 | ||
|
|
de186b95c1 | ||
|
|
6b24513073 | ||
|
|
55ea18128a | ||
|
|
d5557fa512 | ||
|
|
9c97b2aa6d | ||
|
|
1e226a40d1 | ||
|
|
bbe46eaa7d | ||
|
|
7799b191ec | ||
|
|
f3269a3ddf | ||
|
|
a4be822636 | ||
|
|
7cd8dbf7d8 | ||
|
|
654a865007 | ||
|
|
409a0277fb | ||
|
|
7adab1f65f | ||
|
|
3b92c86582 | ||
|
|
de195a1c0f | ||
|
|
d7d2487f6d | ||
|
|
3e31945157 | ||
|
|
c11814c342 | ||
|
|
60e3244088 | ||
|
|
a1ac5f36f0 | ||
|
|
6f38d40613 | ||
|
|
6b7d22de3f | ||
|
|
2f763dceda | ||
|
|
96468c4083 | ||
|
|
858d52563f | ||
|
|
7505b3b271 | ||
|
|
758dc88b55 | ||
|
|
9be418dc65 | ||
|
|
2ac310456e | ||
|
|
ff264d6f7a | ||
|
|
8019c414e1 | ||
|
|
a3fbe3731e | ||
|
|
e3742dbc66 | ||
|
|
83bf717d6d | ||
|
|
7fcdaa1c46 | ||
|
|
a8154f9405 | ||
|
|
52a5afeba5 | ||
|
|
43cc40d7fe | ||
|
|
42a98f000e | ||
|
|
f003ef935b | ||
|
|
e2b2a184c8 | ||
|
|
094595c435 | ||
|
|
4118a86c0d | ||
|
|
928d6f3821 | ||
|
|
b2ade568c8 | ||
|
|
27820ce897 | ||
|
|
fea7e66996 | ||
|
|
474d1571ae | ||
|
|
2ef627b87e | ||
|
|
2dfb82b623 | ||
|
|
ccd7fdfd52 | ||
|
|
f92e59ed34 | ||
|
|
d29477349a | ||
|
|
259e480baa | ||
|
|
0055dd1142 | ||
|
|
61c44780f0 | ||
|
|
9710d3b15c | ||
|
|
04cefebd75 | ||
|
|
dbd927c002 | ||
|
|
e6b2116a2b | ||
|
|
855a02091f | ||
|
|
358434429e | ||
|
|
b00061d85e | ||
|
|
022f08c05e | ||
|
|
6ff5c8ce5f | ||
|
|
3be7fb9dd8 | ||
|
|
72dd970b6e | ||
|
|
f4d09ece43 | ||
|
|
239e6085ba | ||
|
|
b17eab3aeb | ||
|
|
752f7fcde0 | ||
|
|
f6b061f787 | ||
|
|
cc8a1290ee | ||
|
|
b7cf893169 | ||
|
|
1458cacdbb | ||
|
|
a48a1bd5d8 | ||
|
|
1acd0a14ba | ||
|
|
6c9ac7636b | ||
|
|
d261a23dbc | ||
|
|
8b7947a172 | ||
|
|
89789b1e3b | ||
|
|
43dd24c480 | ||
|
|
ae6b1d402a | ||
|
|
3054ad33b2 | ||
|
|
a4dbb1a90b | ||
|
|
689150d78a | ||
|
|
fba096f23d | ||
|
|
7d5a3391f1 | ||
|
|
dcef99bf49 | ||
|
|
805fcc22a5 | ||
|
|
c5b7da8ffd | ||
|
|
97f1f7b130 | ||
|
|
0ac460fce3 | ||
|
|
82dfca8272 | ||
|
|
d1c5576d4f | ||
|
|
7090e3ed60 | ||
|
|
9926fd2e79 | ||
|
|
7793cb20d1 | ||
|
|
8ed0fb6a07 | ||
|
|
f5a1222fc5 | ||
|
|
2b892296b0 | ||
|
|
f796802ed3 | ||
|
|
b2ac227877 | ||
|
|
f0aec4efdc | ||
|
|
8d7cdf9d3b | ||
|
|
2ed0380e4f | ||
|
|
752cfa6bf9 | ||
|
|
f11c4dd7ec | ||
|
|
386f60ca6d | ||
|
|
e98f279f64 | ||
|
|
fe190f20f3 | ||
|
|
96855ea391 | ||
|
|
57150d992e | ||
|
|
8b75bf347f | ||
|
|
788fbdbe5e | ||
|
|
b2239374bc | ||
|
|
77c61abef8 | ||
|
|
c2472cc05a | ||
|
|
608fe1c586 | ||
|
|
4413295b27 | ||
|
|
bcc433ba9a | ||
|
|
2ba571d230 | ||
|
|
4a0a1ef053 | ||
|
|
7e882eedf8 | ||
|
|
224e606439 | ||
|
|
14b3344a41 | ||
|
|
a538ba64d2 | ||
|
|
b3bfad7fea | ||
|
|
f80b45e36a | ||
|
|
f3d60b141f | ||
|
|
5463dc261c | ||
|
|
55df0e3c9d | ||
|
|
730aab3e3d | ||
|
|
63740c02c7 | ||
|
|
c5942bcfcd | ||
|
|
c4547c51a2 | ||
|
|
cf89046ede | ||
|
|
d5d44fb64b | ||
|
|
4bb092c345 | ||
|
|
e1f7e94e36 | ||
|
|
2e9d5f7df1 | ||
|
|
8c943e8817 | ||
|
|
56821553fe | ||
|
|
b512ceb217 | ||
|
|
cfeb18de3f | ||
|
|
b407be933d | ||
|
|
4e1920471d | ||
|
|
6ee752aed8 | ||
|
|
a1922efab5 | ||
|
|
76332a759b | ||
|
|
dfdda8a5b8 | ||
|
|
286ee6b1bd | ||
|
|
ef8b0fa41c | ||
|
|
7fbeeae25b | ||
|
|
5cf0553199 | ||
|
|
4f114ef8d3 | ||
|
|
a9d6fbd14d | ||
|
|
3e1629add9 | ||
|
|
f1ac0642d1 | ||
|
|
4689775a1f | ||
|
|
4011733271 | ||
|
|
f41aa234d7 | ||
|
|
a44a3e5b84 | ||
|
|
98a075d062 | ||
|
|
bdc3cb9d6e | ||
|
|
616e4c4908 | ||
|
|
eaf695824b | ||
|
|
69a76f7fb7 | ||
|
|
d70fa46260 | ||
|
|
efba58a36a | ||
|
|
b3a28dca25 | ||
|
|
3b471f0e25 | ||
|
|
aac5fa3537 | ||
|
|
134f7e591e | ||
|
|
5bb2bc3ba4 | ||
|
|
bb6f5cca93 | ||
|
|
81bd8033a9 | ||
|
|
c2a44e9319 | ||
|
|
39917ebfa6 | ||
|
|
9ee06b410e | ||
|
|
76ced2d3b8 | ||
|
|
eaa1ad6578 | ||
|
|
f9095d298d | ||
|
|
fd87954048 | ||
|
|
be249a94de | ||
|
|
59582cf8f5 | ||
|
|
5c70060992 | ||
|
|
ae7f8f1653 | ||
|
|
4d0458b0e8 | ||
|
|
55e6682832 | ||
|
|
65448c017f | ||
|
|
e9fd23d0d3 | ||
|
|
badc857bcd | ||
|
|
7fe80dd3dd | ||
|
|
5feca13cfb | ||
|
|
f267cd9cd9 | ||
|
|
e8c2d4fa95 | ||
|
|
7e415b4542 | ||
|
|
43f4caaba4 | ||
|
|
0c2ba81269 | ||
|
|
3aca339c95 | ||
|
|
41553f6c01 | ||
|
|
3661860433 | ||
|
|
f0210d06ea | ||
|
|
fcfb8cc9a3 | ||
|
|
88391c7942 | ||
|
|
e9c6af562e | ||
|
|
18bac4a063 | ||
|
|
0332118724 | ||
|
|
34b62f7516 | ||
|
|
da1863069d | ||
|
|
31270ca86b | ||
|
|
7e876e3457 | ||
|
|
cbfd991ab7 | ||
|
|
d7cd185da1 | ||
|
|
416504240c | ||
|
|
cfadd2e892 | ||
|
|
3aab13b4da | ||
|
|
76bba1ca05 | ||
|
|
06fcea4116 | ||
|
|
a7fecaf578 | ||
|
|
639940d1bf | ||
|
|
d7397d4563 | ||
|
|
cf07621de7 | ||
|
|
110615b509 | ||
|
|
3915348d03 | ||
|
|
5230694e2c | ||
|
|
68f2fc9174 | ||
|
|
a63802dea7 | ||
|
|
2be388373f | ||
|
|
a62c74f79e | ||
|
|
1a8b5d145f | ||
|
|
794c99239a | ||
|
|
1fee90322d | ||
|
|
ee70111c45 | ||
|
|
79169de8d8 | ||
|
|
74288b0ecb | ||
|
|
a977fb4970 | ||
|
|
79a09362c9 | ||
|
|
94a50086e6 | ||
|
|
18ba54522c | ||
|
|
5483597a5b | ||
|
|
4ed0901957 | ||
|
|
dbbf3ae2a2 | ||
|
|
c3985e3efb | ||
|
|
c28eb90928 | ||
|
|
a55d56f260 | ||
|
|
1d25265c56 | ||
|
|
65f6689025 | ||
|
|
92005b93c3 | ||
|
|
0a7e573117 | ||
|
|
943c1a3c03 | ||
|
|
9156848503 | ||
|
|
2e6ad8be9a | ||
|
|
1aca18324d | ||
|
|
a8a3e48465 | ||
|
|
2e96600529 | ||
|
|
b9074aeb81 | ||
|
|
13add03479 | ||
|
|
bb2dcc51c6 | ||
|
|
8f82e78597 | ||
|
|
34d2a13080 | ||
|
|
6066ed206b | ||
|
|
d30d2792cd | ||
|
|
ba50268634 | ||
|
|
4ca78f46df | ||
|
|
3a7ca0246c | ||
|
|
2cc8b1c2af | ||
|
|
a2d0cbfe5b | ||
|
|
d65c0753ef | ||
|
|
76bd90b228 | ||
|
|
d908403a99 | ||
|
|
0fe775962f | ||
|
|
412a6992c7 | ||
|
|
cfd1992733 | ||
|
|
e86d6169ba | ||
|
|
a379bd2b52 | ||
|
|
1137047606 | ||
|
|
ad4cf0cc3a | ||
|
|
e7d2e0dc2f | ||
|
|
aa51218428 | ||
|
|
38bc5fb71e | ||
|
|
411516ddb9 | ||
|
|
c6c17ac388 | ||
|
|
6838133865 | ||
|
|
4b2bb2271a | ||
|
|
20b7ac8c0d | ||
|
|
ca7c432728 | ||
|
|
3b966191e3 | ||
|
|
a7889984a0 | ||
|
|
3c72d7aaa0 | ||
|
|
ece0030978 | ||
|
|
a26a769119 | ||
|
|
3313de1eec | ||
|
|
7a6e16631b | ||
|
|
ce4d71ba2e | ||
|
|
300cd31efb | ||
|
|
f5bb734441 | ||
|
|
e33560ef82 | ||
|
|
84f34b2244 | ||
|
|
086bae03d3 | ||
|
|
4a0a429774 | ||
|
|
c4142ab4e7 | ||
|
|
95f8046db9 | ||
|
|
d8a87e9f04 | ||
|
|
86dcd6f0ff | ||
|
|
411a14dd3d | ||
|
|
03bc8db64a | ||
|
|
fe7fd8bc79 | ||
|
|
92eb41c117 | ||
|
|
a1fca56aac | ||
|
|
f3630399ee | ||
|
|
5375c656af | ||
|
|
1792d49634 | ||
|
|
de98578fdd | ||
|
|
c0a59a2837 | ||
|
|
29201006d5 | ||
|
|
7a1c780088 | ||
|
|
550f34c128 | ||
|
|
d6b17b76db | ||
|
|
c42e803cfa | ||
|
|
0233c3a11a | ||
|
|
71a2b6956f | ||
|
|
8282557ebe | ||
|
|
083a162d0e | ||
|
|
60487ea02e | ||
|
|
60a8841439 | ||
|
|
184d1eb751 | ||
|
|
9fea30a44d | ||
|
|
04efcbcdb6 | ||
|
|
2b29b5607d | ||
|
|
20f19c2509 | ||
|
|
5d0e66763e | ||
|
|
bcfbc24e93 | ||
|
|
37d805b007 | ||
|
|
6a246e6c57 | ||
|
|
3a0229f60d | ||
|
|
0bf95ec568 | ||
|
|
6f05142447 | ||
|
|
e02108d535 | ||
|
|
b7e668217d | ||
|
|
472ffef5cf | ||
|
|
5acdb208f5 | ||
|
|
7bf5d9cf64 | ||
|
|
34a80180ef | ||
|
|
3bbba9c6cb | ||
|
|
b052c2e029 | ||
|
|
4eb884e2a6 | ||
|
|
3f8ce8ff50 | ||
|
|
67a2371c05 | ||
|
|
80250d32bd | ||
|
|
0fadbe1ce9 | ||
|
|
9c3ee262b4 | ||
|
|
b62eba8128 | ||
|
|
ef114d7727 | ||
|
|
be7d127c9e | ||
|
|
1209150785 | ||
|
|
31940b919e | ||
|
|
c3c990db43 | ||
|
|
84a742336c | ||
|
|
ada8ffd3b9 | ||
|
|
2277d90326 | ||
|
|
cbc27a78ee | ||
|
|
a2189643bd | ||
|
|
a6101b3da9 | ||
|
|
216fb2141b | ||
|
|
afb54ca048 | ||
|
|
f4b23b3a03 | ||
|
|
d6cd593b95 | ||
|
|
2d9c1b3fd9 | ||
|
|
f2f10a06d9 | ||
|
|
e1def884f3 | ||
|
|
fd61c00ad1 | ||
|
|
d8f422f98d | ||
|
|
7f200df1ac | ||
|
|
9b38ed65c0 | ||
|
|
25c20e5093 | ||
|
|
ec8a441649 | ||
|
|
04a91dc71a | ||
|
|
2023926a3a | ||
|
|
75465ac4bf | ||
|
|
2dbd0b3832 | ||
|
|
3b7d97b194 | ||
|
|
a1fab0e744 | ||
|
|
87331f0a3a | ||
|
|
6cd4f1fcaa | ||
|
|
6b84dc09a7 | ||
|
|
d125064430 | ||
|
|
39490b6883 | ||
|
|
e45f9c5a13 | ||
|
|
9ceb5ea3c5 | ||
|
|
16bd52148e | ||
|
|
9a2705c84c | ||
|
|
6c069a4afe | ||
|
|
42cbc46609 | ||
|
|
096026b92a | ||
|
|
060eafac60 | ||
|
|
f6f36750ec | ||
|
|
2e9aa01771 | ||
|
|
40d70cd1d4 | ||
|
|
17ef0dc12b | ||
|
|
e530ad5412 | ||
|
|
882eb85a33 | ||
|
|
6b0726bcde | ||
|
|
712467a7f4 | ||
|
|
0a9e732ea4 | ||
|
|
b101157d67 | ||
|
|
f859c289aa | ||
|
|
1009d2b4bc | ||
|
|
1739114490 | ||
|
|
9724bc07a6 | ||
|
|
24ff3622fd | ||
|
|
f1d1674693 | ||
|
|
c87a88b244 | ||
|
|
8bdcf4ec3b | ||
|
|
5f0188b036 | ||
|
|
bf3037d57e | ||
|
|
5ebd33576a | ||
|
|
4fb3639f71 | ||
|
|
d44a41c587 | ||
|
|
4960c4b97c | ||
|
|
5c51b0632f | ||
|
|
73b0693632 | ||
|
|
0bcb81bedd | ||
|
|
bfa23ddb80 | ||
|
|
28f473da14 | ||
|
|
1fd1202373 | ||
|
|
8845e736e5 | ||
|
|
dd16c2b99d | ||
|
|
e7779bba0a | ||
|
|
701c834115 | ||
|
|
18423e5805 | ||
|
|
b332d9897d | ||
|
|
191db5104a | ||
|
|
bfacd8a780 | ||
|
|
d4bff9d6b1 | ||
|
|
6defa1cd7d | ||
|
|
5b2b5fd967 | ||
|
|
6932498d48 | ||
|
|
e07f800417 | ||
|
|
464e5f1496 | ||
|
|
724e3f2755 | ||
|
|
59c76141ce | ||
|
|
a494428334 | ||
|
|
7145a2d640 | ||
|
|
baaa04151d | ||
|
|
381125becd | ||
|
|
091327afee | ||
|
|
f1d7d7f02a | ||
|
|
84bd4d3d38 | ||
|
|
0367cf8e41 | ||
|
|
afea724e4d | ||
|
|
a4915184cd | ||
|
|
b7a7797293 | ||
|
|
d0e59f1824 | ||
|
|
67befea66e | ||
|
|
f18ebc4d67 | ||
|
|
5324832f13 | ||
|
|
2d9162a4dd | ||
|
|
d9443162ef | ||
|
|
e8dbab99eb | ||
|
|
cdcc04c8ff | ||
|
|
740a1c991e | ||
|
|
70cfe8d693 | ||
|
|
520e711ca6 | ||
|
|
6d1fe32a39 | ||
|
|
1cc548284d | ||
|
|
d49843de0b | ||
|
|
42efd95476 | ||
|
|
2ec68ef1aa | ||
|
|
6e09489685 | ||
|
|
af07c411ee | ||
|
|
a22ca4758c | ||
|
|
bb3ba88f18 | ||
|
|
ce646c590f | ||
|
|
ba8cca55e8 | ||
|
|
ae738fae35 | ||
|
|
2922c8fe77 | ||
|
|
4599740637 | ||
|
|
8d17ca536e | ||
|
|
3fa7b30485 | ||
|
|
d9adb6c1fc | ||
|
|
f4944a05c9 | ||
|
|
6ba49a73df | ||
|
|
babeb1f31d | ||
|
|
13cbb59e5e | ||
|
|
8d2a072645 | ||
|
|
b2244503b1 | ||
|
|
9d67e1c473 | ||
|
|
489d56dad1 | ||
|
|
532c2f57a1 | ||
|
|
efca90507b | ||
|
|
28ff809739 | ||
|
|
8e73421094 | ||
|
|
5bab2af500 | ||
|
|
9b7c6585d8 | ||
|
|
58930c4c5e | ||
|
|
9412226a76 | ||
|
|
df12581879 | ||
|
|
0ec1638ca7 | ||
|
|
2516e18ebb | ||
|
|
d2f8076c70 | ||
|
|
cbfb11015e | ||
|
|
2d90d11e9e | ||
|
|
7ae0d6b7cc | ||
|
|
e0dc2ba03a | ||
|
|
aea6e105e6 | ||
|
|
a082cf73a0 | ||
|
|
61268f36b9 | ||
|
|
99cd0c5c41 | ||
|
|
2266301469 | ||
|
|
30b0012e6a | ||
|
|
77d1958c42 | ||
|
|
88f347f18c | ||
|
|
09cf8885cf | ||
|
|
f3d4eab951 | ||
|
|
866b6cc233 | ||
|
|
cafecad8ae | ||
|
|
0ea673e507 | ||
|
|
cf367c9340 | ||
|
|
d72e15649f | ||
|
|
89a3b77a1d | ||
|
|
38f6f71ae0 | ||
|
|
40caa82e55 | ||
|
|
b9aee3b18c | ||
|
|
3f8d2f2b38 | ||
|
|
c3731d9f17 | ||
|
|
6e4b2d8886 | ||
|
|
cf13c06c57 | ||
|
|
e309e72be3 | ||
|
|
b4d804de7c | ||
|
|
f501ef3269 | ||
|
|
79f1074f93 | ||
|
|
48aa6bf489 | ||
|
|
8ed2aacd9e | ||
|
|
dfc5d914fc | ||
|
|
0a11c45065 | ||
|
|
4ae5de5a3a | ||
|
|
e6959f3c7c | ||
|
|
67af8b4973 | ||
|
|
7794f2596e | ||
|
|
167cf6e352 | ||
|
|
3d778ba481 | ||
|
|
b8f0000b59 | ||
|
|
618ef63017 | ||
|
|
ae8e633716 | ||
|
|
4a23832388 | ||
|
|
02e486d757 | ||
|
|
af6e24f433 | ||
|
|
f9e24f51a7 | ||
|
|
9e9f815809 | ||
|
|
a91561f3c2 | ||
|
|
ff79064ca7 | ||
|
|
1a573e1619 | ||
|
|
511be7386c | ||
|
|
2a0991c259 | ||
|
|
2e454c9723 | ||
|
|
7e6d94c8e0 | ||
|
|
e18b790ccd | ||
|
|
0274417ad1 | ||
|
|
a8a3a93ace | ||
|
|
88bbab216e | ||
|
|
f74434a282 | ||
|
|
ed6f9d8252 | ||
|
|
a19ccab16a | ||
|
|
77369a13f1 | ||
|
|
8231356d2a | ||
|
|
469a942b5a | ||
|
|
0afe6cf5d1 | ||
|
|
75a161fe35 | ||
|
|
eb1ed107ba | ||
|
|
13e2e340ac | ||
|
|
0af62da16b | ||
|
|
9b323cbcc8 | ||
|
|
18a0e2d75c | ||
|
|
87c89a9968 | ||
|
|
e237749abd | ||
|
|
994a36d039 | ||
|
|
c989817373 | ||
|
|
e3fb7340aa | ||
|
|
e4ef2832cd | ||
|
|
881602cf1c | ||
|
|
4c01f171ae | ||
|
|
d9817fcb07 | ||
|
|
a784af3e47 | ||
|
|
c268925330 | ||
|
|
378e8e0330 | ||
|
|
8b3fbb672b | ||
|
|
2d5a86774e | ||
|
|
0865104a77 | ||
|
|
1914b37ee4 | ||
|
|
58ef0abb99 | ||
|
|
13ba680a73 | ||
|
|
f40c6da93b | ||
|
|
fc2e81e89a | ||
|
|
293dd1a140 | ||
|
|
b7fda31678 | ||
|
|
94a04f0dc2 | ||
|
|
2338f85dcf | ||
|
|
fbaccc8e5f | ||
|
|
750db38d38 | ||
|
|
8829e1ca3f | ||
|
|
ab703a4346 | ||
|
|
8d5a7b4e10 | ||
|
|
6ba3049314 | ||
|
|
abe92cd693 | ||
|
|
6c6cf0433a | ||
|
|
211b958c58 | ||
|
|
01a0d43943 | ||
|
|
3133eb5aeb | ||
|
|
76b68bebd7 | ||
|
|
c20e723e34 | ||
|
|
7b7478c0a9 | ||
|
+3 |
ebeb008f9b | ||
|
|
a90d765fca | ||
|
|
af51df2b4d | ||
|
|
076e93ca56 | ||
|
|
47f45409d2 | ||
|
|
759d5b8663 | ||
|
|
c554396572 | ||
|
|
fafd3fb181 | ||
|
|
eb05d547de | ||
|
|
1fa8a5ef25 | ||
|
|
5a350efb40 | ||
|
|
242f86891a | ||
|
|
ce6969d058 | ||
|
|
26f8246b91 | ||
|
|
4baa9b6bc1 | ||
|
|
02c86d0268 | ||
|
|
49f99d69a8 | ||
|
|
659d9cf767 | ||
|
|
4d662d31a1 | ||
|
|
6fe6540bc6 | ||
|
|
ee1904bb2c | ||
|
|
aaa59f1455 | ||
|
|
e29b7e83b8 | ||
|
|
6af473a486 | ||
|
|
bf8f966811 | ||
|
|
c64b663514 | ||
|
|
29314cde75 | ||
|
|
3ea73e75d6 | ||
|
|
d18e23378b | ||
|
|
736f1f594c | ||
|
|
09b1643478 | ||
|
|
86f6e8bac3 | ||
|
|
9c65d02e84 | ||
|
|
be9ab02a7e | ||
|
|
cfefba6d4d | ||
|
|
815e13303b | ||
|
|
7c8dbb3ea3 | ||
|
|
cabd459682 | ||
|
|
33aedbae20 | ||
|
|
d3fbc73a39 | ||
|
|
cfda4757e4 | ||
|
|
895ed48bd6 | ||
|
|
eda328d196 | ||
|
|
1c501d1501 | ||
|
|
08a57f32c5 | ||
|
|
b5a26bc227 | ||
|
|
737b019199 | ||
|
|
b41031626f | ||
|
|
a1fc4bccb9 | ||
|
|
d764b6a32d | ||
|
|
87cb8b170a | ||
|
|
badd0de33c | ||
|
|
124fd8ebca | ||
|
|
5d0dc3f546 | ||
|
|
e4baf15b53 | ||
|
|
a1bd8b6259 | ||
|
|
7987c9f45d | ||
|
|
781d0d92df | ||
|
|
b71d8baccc | ||
|
|
a71d949a0c |
No files matched your search
@@ -0,0 +1,82 @@
|
|||||||
|
# Cal.com Development Guide for AI Agents
|
||||||
|
|
||||||
|
This directory contains comprehensive documentation for AI agents working on the Cal.com codebase.
|
||||||
|
|
||||||
|
## Quick Navigation
|
||||||
|
|
||||||
|
- **[Commands](commands.md)** - Build, test, and development commands
|
||||||
|
- **[Knowledge Base](knowledge-base.md)** - Knowledge base & best practices
|
||||||
|
- **[Architecture Overview](#architecture-overview)** - System structure and patterns
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Cal.com is a monorepo using Yarn workspaces and Turbo for build orchestration. The main application is in `apps/web/` with shared packages in `packages/`.
|
||||||
|
|
||||||
|
### Key Directories
|
||||||
|
|
||||||
|
- `apps/web/` - Main Next.js application
|
||||||
|
- `packages/prisma/` - Database schema and migrations
|
||||||
|
- `packages/trpc/` - API layer using tRPC
|
||||||
|
- `packages/ui/` - Shared UI components
|
||||||
|
- `packages/features/` - Feature-specific code
|
||||||
|
- `packages/app-store/` - Third-party app integrations
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Database Layer
|
||||||
|
|
||||||
|
- **Prisma ORM** with PostgreSQL
|
||||||
|
- Schema in `packages/prisma/schema.prisma`
|
||||||
|
- Always use `select` instead of `include` for better performance
|
||||||
|
- Never expose `credential.key` field in API responses
|
||||||
|
|
||||||
|
### API Layer
|
||||||
|
|
||||||
|
- **tRPC** for type-safe APIs
|
||||||
|
- Routers in `packages/trpc/server/routers/`
|
||||||
|
- Authentication handled via NextAuth.js
|
||||||
|
|
||||||
|
### Frontend
|
||||||
|
|
||||||
|
- **Next.js 13+** with App Router in some areas
|
||||||
|
- **React 18** with TypeScript
|
||||||
|
- **Tailwind CSS** for styling
|
||||||
|
- Internationalization with `next-i18next`
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- Use early returns to reduce nesting
|
||||||
|
- Throw descriptive errors with proper error codes
|
||||||
|
- Prefer composition over prop drilling
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
|
||||||
|
- Avoid O(n²) logic in backend code
|
||||||
|
- Minimize Day.js usage in performance-critical paths
|
||||||
|
- Use `select` queries to only fetch needed data
|
||||||
|
- Consider using `.utc()` for Day.js operations
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Never commit secrets or API keys
|
||||||
|
- Always validate input data
|
||||||
|
- Use proper authentication checks
|
||||||
|
- Never expose sensitive credential fields
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
- **Unit tests** with Vitest
|
||||||
|
- **Integration tests** for complex workflows
|
||||||
|
- **E2E tests** with Playwright
|
||||||
|
- Test files use `.test.ts` or `.spec.ts` extensions
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
For large PRs (>500 lines or >10 files):
|
||||||
|
|
||||||
|
- Split by feature boundaries
|
||||||
|
- Separate database migrations, backend logic, frontend components
|
||||||
|
- Create dependency chains that can be merged sequentially
|
||||||
|
- Pattern: Database → Backend → Frontend → Tests
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Coding Standards & Best Practices
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Import Guidelines
|
||||||
|
|
||||||
|
### Type Imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Use type imports for TypeScript types
|
||||||
|
import type { User } from "@prisma/client";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
// ❌ Bad - Regular import for types
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Code Structure
|
||||||
|
|
||||||
|
### Early Returns
|
||||||
|
|
||||||
|
- Prefer early returns to reduce nesting: `if (!booking) return null;`
|
||||||
|
|
||||||
|
### Composition Over Prop Drilling
|
||||||
|
|
||||||
|
- Use React children and context instead of passing props through multiple components
|
||||||
|
|
||||||
|
### Security Rules
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ NEVER expose credential keys
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
select: {
|
||||||
|
credentials: {
|
||||||
|
select: {
|
||||||
|
key: true, // ❌ NEVER do this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ Good - Never select credential.key field
|
||||||
|
const user = await prisma.user.findFirst({
|
||||||
|
select: {
|
||||||
|
credentials: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
type: true,
|
||||||
|
// key field is excluded for security
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Build, Test & Development Commands
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
- `yarn dev` - Start development server for web app
|
||||||
|
- `yarn dev:all` - Start web, website, and console apps
|
||||||
|
- `yarn dev:api` - Start web app with API proxy and API
|
||||||
|
- `yarn dev:console` - Start web app with console
|
||||||
|
- `yarn dx` - Start development with database setup
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
- `yarn build` - Build all packages and apps
|
||||||
|
- `yarn build:ai` - Build AI package specifically
|
||||||
|
- `yarn clean` - Remove build artifacts (node_modules, .next, .turbo, dist)
|
||||||
|
|
||||||
|
## Lint & Type Check
|
||||||
|
|
||||||
|
- `yarn lint` - Run ESLint on codebase
|
||||||
|
- `yarn lint:fix` - Run ESLint and fix issues
|
||||||
|
- `yarn lint:report` - Generate lint report
|
||||||
|
- `yarn type-check` - Run TypeScript type checking
|
||||||
|
- `yarn format` - Format code with Prettier
|
||||||
|
|
||||||
|
## Testing Commands
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
- `yarn test` - Run unit tests (vitest)
|
||||||
|
- `yarn test <filename>` - Run tests for specific file
|
||||||
|
- `yarn test <filename> -- --integrationTestsOnly` - Run integration tests for specific file
|
||||||
|
- `yarn test <filename> -t "<testName>" -- --integrationTestsOnly` - Run specific test by name
|
||||||
|
- `yarn tdd` - Run tests in watch mode
|
||||||
|
- `yarn test:ui` - Run tests with UI interface
|
||||||
|
|
||||||
|
### End-to-End Tests
|
||||||
|
|
||||||
|
- `yarn e2e` - Run end-to-end tests (Playwright)
|
||||||
|
- `yarn e2e <filename>` - Run E2E tests for specific file
|
||||||
|
- `yarn e2e <filename> --grep "<testName>"` - Run specific E2E test by name
|
||||||
|
- `yarn e2e:app-store` - Run app store E2E tests
|
||||||
|
- `yarn e2e:embed` - Run embed E2E tests
|
||||||
|
- `yarn test-e2e` - Run database seed + E2E tests
|
||||||
|
|
||||||
|
## Database Commands
|
||||||
|
|
||||||
|
- `yarn prisma` - Run Prisma CLI commands
|
||||||
|
- `yarn db-seed` - Seed database with test data
|
||||||
|
- `yarn db-deploy` - Deploy database migrations
|
||||||
|
- `yarn db-studio` - Open Prisma Studio
|
||||||
|
- `psql "postgresql://postgres:@localhost:5432/calendso"` - Connect to local PostgreSQL database
|
||||||
|
|
||||||
|
## App Store Commands
|
||||||
|
|
||||||
|
- `yarn create-app` - Create new app store integration
|
||||||
|
- `yarn edit-app` - Edit existing app
|
||||||
|
- `yarn delete-app` - Delete app
|
||||||
|
- `yarn app-store:build` - Build app store
|
||||||
|
- `yarn app-store:watch` - Watch app store for changes
|
||||||
|
|
||||||
|
## Useful Development Patterns
|
||||||
|
|
||||||
|
### Running Single Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit test specific file
|
||||||
|
yarn vitest run packages/lib/some-file.test.ts
|
||||||
|
|
||||||
|
# Integration test specific file
|
||||||
|
yarn test routing-form-response-denormalized.integration-test.ts -- --integrationTestsOnly
|
||||||
|
|
||||||
|
# E2E test specific file
|
||||||
|
yarn e2e tests/booking-flow.e2e.ts
|
||||||
|
|
||||||
|
# Run specific test by name
|
||||||
|
yarn e2e tests/booking-flow.e2e.ts --grep "should create booking"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
|
||||||
|
- Copy `.env.example` to `.env` and configure
|
||||||
|
- Copy `.env.appStore.example` to `.env.appStore` for app store development
|
||||||
|
- Run `yarn dx` for initial development setup with database
|
||||||
@@ -0,0 +1,596 @@
|
|||||||
|
# Knowledge Base - Product & Domain-Specific Information
|
||||||
|
|
||||||
|
## Repo note for calcom/cal.com
|
||||||
|
|
||||||
|
The whole thing is a monorepo. You need to be working in the apps/web folder.
|
||||||
|
|
||||||
|
Linting and Formatting
|
||||||
|
- Run lint with report generation: `yarn lint:report`
|
||||||
|
- Run type checking: `yarn type-check:ci --force`
|
||||||
|
- Run auto-fix: `yarn lint -- --fix`
|
||||||
|
|
||||||
|
Development
|
||||||
|
- Install dependencies: `yarn`
|
||||||
|
- Set up environment:
|
||||||
|
- Copy `.env.example` to `.env`
|
||||||
|
- Generate keys with `openssl rand -base64 32` for both `NEXTAUTH_SECRET` and `CALENDSO_ENCRYPTION_KEY`
|
||||||
|
- Configure Postgres database URL in `.env`
|
||||||
|
- Set DATABASE_DIRECT_URL to the same value as DATABASE_URL
|
||||||
|
|
||||||
|
Database Setup
|
||||||
|
- Development: `yarn workspace @calcom/prisma db-migrate`
|
||||||
|
- Production: `yarn workspace @calcom/prisma db-deploy`
|
||||||
|
|
||||||
|
When setting up local development database, it'll create a bunch of users for you. The passwords are the same as the username. e.g. 'free:free' and 'pro:pro'
|
||||||
|
|
||||||
|
PR Requirements
|
||||||
|
- PR title must follow Conventional Commits specification
|
||||||
|
- For most PRs, you only need to run linting and type checking
|
||||||
|
- E2E tests will only run if PR has "ready-for-e2e" label
|
||||||
|
|
||||||
|
Logging
|
||||||
|
- Control logging verbosity by setting `NEXT_PUBLIC_LOGGER_LEVEL` in .env:
|
||||||
|
- 0: silly
|
||||||
|
- 1: trace
|
||||||
|
- 2: debug
|
||||||
|
- 3: info
|
||||||
|
- 4: warn
|
||||||
|
- 5: error
|
||||||
|
- 6: fatal
|
||||||
|
|
||||||
|
## When addressing issues in the Cal.com repository
|
||||||
|
|
||||||
|
When working on the Cal.com repository, prioritize fixing type issues before addressing failing tests. Running `yarn type-check:ci --force` to identify and fix TypeScript errors should be done first, as these errors are often the root cause of test failures. Only after resolving type issues should you move on to fixing failing tests with `TZ=UTC yarn test`.
|
||||||
|
|
||||||
|
## When creating pull requests
|
||||||
|
|
||||||
|
Create pull requests in draft mode by default, so that actual human can mark it as ready for review only when it is.
|
||||||
|
|
||||||
|
## When developing Playwright tests in the Cal.com repository
|
||||||
|
|
||||||
|
Always ensure Playwright tests pass locally before pushing code. The user requires fast local e2e feedback loops instead of relying on CI, which is too slow for development iteration. Never push test code until those tests are passing locally first.
|
||||||
|
|
||||||
|
## When fixing failing tests in the Cal.com repository
|
||||||
|
|
||||||
|
When fixing failing tests in the Cal.com repository, take an incremental approach by addressing one file at a time rather than attempting to fix all issues simultaneously. This methodical approach makes it easier to identify and resolve specific issues without getting overwhelmed by the complexity of multiple failing tests across different files. Focus on getting each file's tests passing completely before moving on to the next file.
|
||||||
|
|
||||||
|
To identify and fix issues in the Cal.com codebase:
|
||||||
|
|
||||||
|
1. Run `yarn type-check:ci --force` to identify TypeScript type errors and get fresh results always, bypassing any caching issues
|
||||||
|
2. Run `yarn test` to identify failing unit tests
|
||||||
|
3. Address both type errors and failing tests before considering the task complete
|
||||||
|
4. Type errors often need to be fixed first as they may be causing the test failures
|
||||||
|
|
||||||
|
## When implementing mock services in the Cal.com test files
|
||||||
|
|
||||||
|
When mocking calendar services in Cal.com test files, implement the Calendar interface rather than adding individual properties from each specific calendar service type (like FeishuCalendarService). Since all calendar services implement the Calendar interface and are stored in a map, the mock service should also implement this interface to ensure type compatibility. This approach is more maintainable than trying to add all properties from each specific calendar service implementation. For complex mocks that cause type compatibility issues with deep mocks, consider using simpler fake implementations that directly implement the required interfaces instead of trying to match all properties of the original service classes. When needed, you can modify other mock files to support your implementation rather than trying to force compatibility with existing mocks.
|
||||||
|
|
||||||
|
## When implementing mocks for Cal.com app-store integrations
|
||||||
|
|
||||||
|
When mocking app-store resources in Cal.com tests, prefer implementing simpler mock designs that directly implement the required interfaces rather than trying to match complex deep mock structures created with mockDeep. This approach is more maintainable and helps resolve type compatibility issues. The user encourages creative solutions and refactoring to better designs when the standard mocking approach causes persistent type errors.
|
||||||
|
|
||||||
|
## When implementing workflow triggers in the Cal.com repository
|
||||||
|
|
||||||
|
To trigger workflows in Cal.com, use the `scheduleWorkflowReminders` function. This is the standard approach used throughout the codebase for triggering any workflow. Before implementing new workflow triggers, examine existing implementations in the codebase to understand the pattern. The function filters workflows by trigger type and processes each workflow step. Key locations where this is used include booking handlers, confirmation processes, and other booking-related events.
|
||||||
|
|
||||||
|
## When making changes to Prisma schema in Cal.com
|
||||||
|
|
||||||
|
After making changes to the Prisma schema in Cal.com and creating migrations, you need to run `yarn prisma generate` to update the TypeScript types. This is especially important when switching Node.js versions, after adding new fields to models, or after pulling changes that include Prisma schema updates, as it ensures the TypeScript compiler recognizes the updated schema structure. If you encounter errors related to missing enum values (like CreationSource.WEBAPP), running `yarn prisma generate` will typically resolve these issues by regenerating the TypeScript types from the schema.
|
||||||
|
|
||||||
|
If you encounter enum generator errors during the Prisma generate step (like "Cannot find module './enum-generator.ts'"), run `yarn install` first before trying to generate. This ensures all dependencies are properly installed before the generation process.
|
||||||
|
|
||||||
|
Whenever you change the schema.prisma file, remember to always consolidate migrations by squashing them as declared in the Prisma docs: https://www.prisma.io/docs/orm/prisma-migrate/workflows/squashing-migrations. This helps maintain a clean migration history and prevents accumulation of multiple migration files.
|
||||||
|
|
||||||
|
## When reviewing PRs in the Cal.com repository
|
||||||
|
|
||||||
|
When asked to review a PR, focus on providing a clear summary of what the PR is doing and its core functionality. Avoid getting sidetracked by CI failures, testing issues, or technical implementation details unless specifically requested. The user prefers concise, focused reviews that prioritize understanding the main purpose and changes of the PR.
|
||||||
|
|
||||||
|
## When running Playwright tests in the Cal.com repository
|
||||||
|
|
||||||
|
Use the command format `PLAYWRIGHT_HEADLESS=1 yarn e2e [test-file.e2e.ts]` to run Playwright tests instead of the standard `yarn playwright test` command. This format includes the proper timezone setting, virtual display server, and uses the repository's e2e runner.
|
||||||
|
|
||||||
|
## When running tests in the Cal.com repository
|
||||||
|
|
||||||
|
When running tests in the Cal.com repository, use the TZ=UTC environment variable (e.g., `TZ=UTC yarn test`) to ensure consistent timezone handling. This prevents timezone-related test failures that might occur when tests are run in different environments or by different developers with varying local timezone settings.
|
||||||
|
|
||||||
|
## When working on calendar cache features in the Cal.com codebase
|
||||||
|
|
||||||
|
The calendar cache system follows specific patterns in `packages/features/calendar-cache-sql`. When implementing provider-specific calendar cache services (like for Outlook/Office365), the provider-specific code should be placed in the corresponding provider directory (e.g., `packages/app-store/office365calendar`).
|
||||||
|
|
||||||
|
## When working on PRs in the Cal.com repository
|
||||||
|
|
||||||
|
When making changes to the Cal.com codebase, always run type checks locally using `yarn type-check:ci` before concluding that CI failures are unrelated to your changes. Even if errors appear in files you haven't directly modified, your changes might still be causing type issues through dependencies or type inference. Compare type check results between the main branch and your feature branch to confirm whether you've introduced new type errors.
|
||||||
|
|
||||||
|
## When working on type issues in the Cal.com repository
|
||||||
|
|
||||||
|
Type casting with "as any" is strictly forbidden in the Cal.com codebase. When encountering Prisma type incompatibilities or other TypeScript type issues, proper type-safe solutions must be used instead, such as Prisma extensions system, type parameter constraints, repository pattern isolation, explicit type definitions, and extension composition patterns that are already established in the codebase.
|
||||||
|
|
||||||
|
## When working on UI layout changes in the Cal.com web application
|
||||||
|
|
||||||
|
The event types page UI components are located in `apps/web/modules/event-types/views/event-types-listing-view.tsx`. This file contains the layout implementation for the search bar and team tabs components on the event types listing page.
|
||||||
|
|
||||||
|
Changes to shared UI patterns (like tab layouts and button alignments) need to be checked across multiple views to maintain consistency:
|
||||||
|
- Event types page layout: `apps/web/modules/event-types/views/event-types-listing-view.tsx`
|
||||||
|
- Bookings page layout: `apps/web/modules/bookings/views/bookings-listing-view.tsx`
|
||||||
|
- Common elements like tabs, search bars, and filter buttons should maintain consistent alignment across views
|
||||||
|
|
||||||
|
## When working on workflow triggers or similar enum-based features in the Cal.com codebase
|
||||||
|
|
||||||
|
To add new workflow triggers:
|
||||||
|
|
||||||
|
1. Check packages/prisma/schema.prisma for existing webhooks and workflow trigger enums as reference
|
||||||
|
2. Add the same enums to workflows (only when asked by user specifically, or else focus on users requirement)
|
||||||
|
3. Add enums to packages/features/ee/workflows/lib/constants.ts for UI display
|
||||||
|
4. Add translations to en/locale.json using the format {enum}_trigger (all lowercase). Webhook triggers serve as the reference implementation pattern for workflow triggers.
|
||||||
|
|
||||||
|
## When working with app-store integrations in Cal.com
|
||||||
|
|
||||||
|
The Cal.com repository uses generated files (*.generated.ts) for app-store integrations. These files are created by the app-store-cli tool. When making structural changes to how integrations are imported or used, you need to update CLI code that generates these files. Typically manual changes to the *.generated.ts files are not made. That was only for PR 19771 for a proof-of-concept. Recent changes have moved from dynamic imports to static map-based imports for better performance. When working with browser components in the app-store, static imports should be used rather than dynamic imports (using Next.js' dynamic function) to maintain consistency with the performance improvements.
|
||||||
|
|
||||||
|
When modifying the app-store-cli build.ts file, you must ensure it correctly handles all types of generated files:
|
||||||
|
1. Regular service files (calendar.services.generated.ts, crm.services.generated.ts, etc.) need default imports
|
||||||
|
2. Browser component files (apps.browser-addon.generated.tsx, etc.) may require dynamic imports with Next.js
|
||||||
|
3. After making changes to build.ts, always verify all generated files have the correct imports by checking each file type
|
||||||
|
|
||||||
|
The lazyImport parameter in getExportedObject() determines whether to use dynamic imports (for browser components) or static imports (for server-side services).
|
||||||
|
|
||||||
|
## When working with branches in the Cal.com repository
|
||||||
|
|
||||||
|
When asked to move changes to a different branch in the Cal.com repository, use git commands to commit existing changes to the specified branch rather than redoing the work. This is more efficient and prevents duplication of effort. The user prefers direct branch operations over reimplementing the same changes multiple times.
|
||||||
|
|
||||||
|
## When working with calendar events in the Cal.com codebase
|
||||||
|
|
||||||
|
Cal.com events in Google Calendar can be identified by checking if the iCalUID ends with "@Cal.com" (e.g., "2GBXSdEixretciJfKVmYN8@Cal.com"). This identifier is used to distinguish Cal.com bookings from other calendar events for data storage and privacy purposes.
|
||||||
|
|
||||||
|
## When working with CI/CD in the Cal.com repository
|
||||||
|
|
||||||
|
When reviewing CI check failures in Cal.com:
|
||||||
|
1. E2E tests can be flaky and may fail intermittently
|
||||||
|
2. Focus only on CI failures that are directly related to your code changes
|
||||||
|
3. Infrastructure-related failures (like dependency installation issues) can be disregarded if all code-specific checks (type checking, linting, unit tests) are passing
|
||||||
|
|
||||||
|
## When working with database models in Cal.com
|
||||||
|
|
||||||
|
Database models in Cal.com are defined in `packages/prisma/schema.prisma`. When adding new fields to models:
|
||||||
|
|
||||||
|
1. For timestamp fields like `createdAt` and `updatedAt`:
|
||||||
|
- Do not set default values if you want existing records to have null values
|
||||||
|
- Only new records should get timestamps automatically
|
||||||
|
- For `updatedAt` fields, ensure they're updated when records are modified
|
||||||
|
|
||||||
|
2. Create a migration using `npx prisma migrate dev --name migration_name` to update the database schema
|
||||||
|
|
||||||
|
3. When implementing cache-related features that require timestamp tracking, always update the database schema first before modifying application code that references those fields. The schema changes must be completed and migrated before the application can successfully query or use the new fields.
|
||||||
|
|
||||||
|
## When working with generated files in the Cal.com repository
|
||||||
|
|
||||||
|
When fixing imports in Cal.com's generated files (like packages/app-store/apps.browser-*.generated.tsx), always check the actual exports in the source files first. For EventTypeAppCardInterface components, they likely use named exports rather than default exports, requiring imports like `import * as ComponentName from "./path"` instead of `import ComponentName from "./path"`. This verification step is crucial when working with browser-addon, browser-appsettings, browser-eventtypesettings, and browser-install generated files. For these files, if you're seeing import errors, check whether the components are exported as default exports in their source files and adjust your import statements accordingly.
|
||||||
|
|
||||||
|
## When working with git and CI systems
|
||||||
|
|
||||||
|
Always push committed changes to the remote repository before waiting for or checking CI status. Waiting for CI checks on unpushed local commits is backwards - the CI runs on the remote repository state, not local commits. The proper sequence is: commit locally, run local checks, push to remote, then monitor CI status.
|
||||||
|
|
||||||
|
## When working with imports/exports in the Cal.com codebase
|
||||||
|
|
||||||
|
When working with imports in the Cal.com codebase, particularly in app-store integrations, pay attention to whether modules use named exports or default exports. Many services like VideoApiAdapter, CalendarService, and PaymentService are exported as named exports, but the actual export name may differ from the generic service type (e.g., `export class AppleCalendarService` instead of `export class CalendarService`). When importing these services, verify the actual export name in the source file and use the appropriate named import syntax (e.g., `import { AppleCalendarService } from "./applecalendar/lib/CalendarService"` or with renaming `import { AppleCalendarService as ApplecalendarCalendarService } from "./applecalendar/lib/CalendarService"`). Always check the actual export pattern and name in the source file to determine the correct import syntax.
|
||||||
|
|
||||||
|
## When working with tRPC components in the Cal.com repository
|
||||||
|
|
||||||
|
When making changes that affect tRPC components or after pulling updates that modify tRPC-related files, you need to follow a specific build order:
|
||||||
|
1. First run `yarn prisma generate` to ensure all database types are up-to-date
|
||||||
|
2. Then run `cd packages/trpc && yarn build` to rebuild the tRPC package
|
||||||
|
This sequence ensures that type definitions are properly generated before building the tRPC components that depend on them, preventing type errors during compilation.
|
||||||
|
|
||||||
|
## When working with workflows or webhooks in the Cal.com repository
|
||||||
|
|
||||||
|
Workflows and webhooks are two completely separate features in Cal.com with different implementations and file structures. The workflow constants file is located at `packages/features/ee/workflows/lib/constants.ts`, not in the webhooks directory. When working on workflow triggers, do not reference or use webhook trigger implementations - they are distinct systems that should not be confused or mixed.
|
||||||
|
|
||||||
|
## When adding new feature flags to the Cal.com repository
|
||||||
|
|
||||||
|
To seed new feature flags in Cal.com, create a Prisma migration using the command `yarn prisma migrate dev --create-only --name seed_[feature_name]_feature`. The migration file should be placed in `packages/prisma/migrations/` with a timestamp prefix format like `20250724210733_seed_calendar_cache_sql_features/migration.sql`. Follow the pattern from existing feature seeding migrations like `packages/prisma/migrations/20241216000000_add_calendar_cache_serve/migration.sql` for the SQL structure. The migration should INSERT the new features into the `Feature` table with appropriate type (like `OPERATIONAL`) and default `enabled` status for manual team enablement.
|
||||||
|
|
||||||
|
## When adding new UI elements or text strings to Cal.com
|
||||||
|
|
||||||
|
All UI strings in Cal.com must be properly translated using the i18n system. This includes:
|
||||||
|
- Labels for new UI elements (like dropdown labels, settings headers)
|
||||||
|
- Option values that are displayed to users
|
||||||
|
- Any text that appears in the interface
|
||||||
|
|
||||||
|
Even if some related strings are already translated (like "Bookings" and "Insights"), new strings must be explicitly added to the translation system.
|
||||||
|
|
||||||
|
## When creating a pull request
|
||||||
|
|
||||||
|
### Title
|
||||||
|
|
||||||
|
- Use conventional commits: `feat:`, `fix:`, `refactor:`
|
||||||
|
- Be specific: `fix: handle timezone edge case in booking creation`
|
||||||
|
- Not generic: `fix: booking bug`
|
||||||
|
|
||||||
|
### Size Limits
|
||||||
|
|
||||||
|
- **Large PRs** (>500 lines or >10 files) are not recommended.
|
||||||
|
- Guide the user how to split large PRs into smaller ones.
|
||||||
|
|
||||||
|
## When handling errors
|
||||||
|
|
||||||
|
### Descriptive Errors
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Descriptive error with context
|
||||||
|
throw new Error(`Unable to create booking: User ${userId} has no available time slots for ${date}`);
|
||||||
|
|
||||||
|
// ❌ Bad - Generic error
|
||||||
|
throw new Error("Booking failed");
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Use proper error classes
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "Invalid booking time slot",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Performance Guidelines
|
||||||
|
|
||||||
|
- Aim for O(n) or O(n log n) complexity, avoid O(n²)
|
||||||
|
- Use database-level filtering instead of JavaScript filtering
|
||||||
|
- Consider pagination for large datasets
|
||||||
|
- Use database transactions for related operations
|
||||||
|
|
||||||
|
## When querying database
|
||||||
|
|
||||||
|
Prefer Select over Include:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Use select for performance and security
|
||||||
|
const booking = await prisma.booking.findFirst({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ❌ Bad - Include fetches all fields
|
||||||
|
const booking = await prisma.booking.findFirst({
|
||||||
|
include: {
|
||||||
|
user: true, // This gets ALL user fields
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Avoid barrel imports
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Bad - Avoid importing from index.ts barrel files
|
||||||
|
import { BookingService, UserService } from "./services";
|
||||||
|
|
||||||
|
// ✅ Good - Import directly from source files
|
||||||
|
import { BookingService } from "./services/BookingService";
|
||||||
|
import { UserService } from "./services/UserService";
|
||||||
|
|
||||||
|
|
||||||
|
// ❌ Bad
|
||||||
|
import { Button } from "@calcom/ui";
|
||||||
|
|
||||||
|
// ✅ Good - Import directly from source files
|
||||||
|
import { Button } from "@calcom/ui/components/button";
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Naming Conventions
|
||||||
|
|
||||||
|
### Repository Files
|
||||||
|
|
||||||
|
- **Must** include `Repository` suffix, PascalCase matching class: `PrismaBookingRepository.ts`
|
||||||
|
|
||||||
|
### Service Files
|
||||||
|
|
||||||
|
- **Must** include `Service` suffix, PascalCase matching class, avoid generic names: `MembershipService.ts`
|
||||||
|
|
||||||
|
### General Files
|
||||||
|
|
||||||
|
- **Components**: PascalCase (e.g., `BookingForm.tsx`)
|
||||||
|
- **Utilities**: kebab-case (e.g., `date-utils.ts`)
|
||||||
|
- **Types**: PascalCase with `.types.ts` suffix (e.g., `Booking.types.ts`)
|
||||||
|
- **Tests**: Same as source file + `.test.ts` or `.spec.ts`
|
||||||
|
- **Avoid**: Dot-suffixes like `.service.ts`, `.repository.ts` (except for tests, types, specs)
|
||||||
|
|
||||||
|
## Repository Method Conventions
|
||||||
|
|
||||||
|
Repositories should follow consistent naming and design patterns to promote reusability and maintainability.
|
||||||
|
|
||||||
|
### Method Naming Rules
|
||||||
|
|
||||||
|
**1. Don't include the repository's entity name in method names**
|
||||||
|
|
||||||
|
Method names should be concise and avoid redundancy since the repository class name already indicates the entity type.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Concise method names
|
||||||
|
class BookingRepository {
|
||||||
|
findById(id: string) { ... }
|
||||||
|
findByUserId(userId: string) { ... }
|
||||||
|
create(data: BookingCreateInput) { ... }
|
||||||
|
delete(id: string) { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Redundant entity name in methods
|
||||||
|
class BookingRepository {
|
||||||
|
findBookingById(id: string) { ... }
|
||||||
|
findBookingByUserId(userId: string) { ... }
|
||||||
|
createBooking(data: BookingCreateInput) { ... }
|
||||||
|
deleteBooking(id: string) { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Use `include` or similar keywords for methods that fetch relational data**
|
||||||
|
|
||||||
|
When a method retrieves additional related entities, make this explicit in the method name using keywords like `include`, `with`, or `andRelations`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Clear indication of included relations
|
||||||
|
class EventTypeRepository {
|
||||||
|
findById(id: string) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findByIdIncludeHosts(id: string) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
hosts: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findByIdIncludeHostsAndSchedule(id: string) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
hosts: true,
|
||||||
|
schedule: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Unclear what data is included
|
||||||
|
class EventTypeRepository {
|
||||||
|
findById(id: string) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
hosts: true,
|
||||||
|
schedule: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findByIdForReporting(id: string) {
|
||||||
|
return prisma.eventType.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
hosts: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Keep methods generic and reusable - avoid use-case-specific names**
|
||||||
|
|
||||||
|
Repository methods should be general-purpose and describe what data they return, not how or where it's used. This promotes code reuse across different features.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Generic, reusable methods
|
||||||
|
class BookingRepository {
|
||||||
|
findByUserIdIncludeAttendees(userId: string) {
|
||||||
|
return prisma.booking.findMany({
|
||||||
|
where: { userId },
|
||||||
|
include: {
|
||||||
|
attendees: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findByDateRangeIncludeEventType(startDate: Date, endDate: Date) {
|
||||||
|
return prisma.booking.findMany({
|
||||||
|
where: {
|
||||||
|
startTime: { gte: startDate },
|
||||||
|
endTime: { lte: endDate },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
eventType: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Use-case-specific method names
|
||||||
|
class BookingRepository {
|
||||||
|
findBookingsForReporting(userId: string) {
|
||||||
|
return prisma.booking.findMany({
|
||||||
|
where: { userId },
|
||||||
|
include: {
|
||||||
|
attendees: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findBookingsForDashboard(startDate: Date, endDate: Date) {
|
||||||
|
return prisma.booking.findMany({
|
||||||
|
where: {
|
||||||
|
startTime: { gte: startDate },
|
||||||
|
endTime: { lte: endDate },
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
eventType: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. No business logic in repositories**
|
||||||
|
|
||||||
|
Repositories should only handle data access. Business logic, validations, and complex transformations belong in the Service layer.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Good - Repository only handles data access
|
||||||
|
class BookingRepository {
|
||||||
|
findByIdIncludeAttendees(id: string) {
|
||||||
|
return prisma.booking.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
attendees: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateStatus(id: string, status: BookingStatus) {
|
||||||
|
return prisma.booking.update({
|
||||||
|
where: { id },
|
||||||
|
data: { status },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BookingService {
|
||||||
|
async confirmBooking(bookingId: string) {
|
||||||
|
const booking = await this.bookingRepository.findByIdIncludeAttendees(bookingId);
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
throw new Error("Booking not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (booking.status !== "PENDING") {
|
||||||
|
throw new Error("Only pending bookings can be confirmed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Business logic: send confirmation emails
|
||||||
|
await this.emailService.sendConfirmationToAttendees(booking.attendees);
|
||||||
|
|
||||||
|
// Update status through repository
|
||||||
|
return this.bookingRepository.updateStatus(bookingId, "CONFIRMED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Bad - Business logic in repository
|
||||||
|
class BookingRepository {
|
||||||
|
async confirmBooking(bookingId: string) {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: { id: bookingId },
|
||||||
|
include: {
|
||||||
|
attendees: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking) {
|
||||||
|
throw new Error("Booking not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (booking.status !== "PENDING") {
|
||||||
|
throw new Error("Only pending bookings can be confirmed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Business logic shouldn't be here
|
||||||
|
await sendEmailToAttendees(booking.attendees);
|
||||||
|
|
||||||
|
return prisma.booking.update({
|
||||||
|
where: { id: bookingId },
|
||||||
|
data: { status: "CONFIRMED" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
- Method names should be concise: `findById` not `findBookingById`
|
||||||
|
- Use `include`/`with` keywords when fetching relations: `findByIdIncludeHosts`
|
||||||
|
- Keep methods generic and reusable: `findByUserIdIncludeAttendees` not `findBookingsForReporting`
|
||||||
|
- No business logic in repositories - that belongs in Services
|
||||||
|
|
||||||
|
## When using Day.js
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ⚠️ Slow in performance-critical code (loops)
|
||||||
|
dates.map((date) => dayjs(date).add(1, "day").format());
|
||||||
|
|
||||||
|
// ✅ Better - Use .utc() for performance
|
||||||
|
dates.map((date) => dayjs.utc(date).add(1, "day").format());
|
||||||
|
|
||||||
|
// ✅ Best - Use native Date when possible
|
||||||
|
dates.map((date) => new Date(date.valueOf() + 24 * 60 * 60 * 1000));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next.js App Directory: Authorisation Checks in Pages
|
||||||
|
|
||||||
|
This can include checking session.user exists or session.org etc.
|
||||||
|
|
||||||
|
### TL;DR:
|
||||||
|
|
||||||
|
Don’t put permission checks in layout.tsx! Always put them directly inside your page.tsx or relevant server components for every restricted route.
|
||||||
|
|
||||||
|
|
||||||
|
### Why Not to Use Layouts for Permission Checks
|
||||||
|
|
||||||
|
* Layouts don’t intercept all requests: If a user navigates directly or refreshes a protected route, layout checks might be skipped, exposing sensitive content.
|
||||||
|
* APIs and server actions bypass layouts: Sensitive operations running on the server can’t be guarded by checks in the layout.
|
||||||
|
* Risk of data leaks: Only page/server-level checks ensure that unauthorized users never get protected data.
|
||||||
|
|
||||||
|
### ✅ How To Secure Routes (The Right Way)
|
||||||
|
|
||||||
|
* Check permissions inside page.tsx or the actual server component.
|
||||||
|
* Perform all session/user/role validation before querying or rendering sensitive content.
|
||||||
|
* Redirect or return nothing to unauthorized users, before running restricted code.
|
||||||
|
|
||||||
|
### 🛠️ Example: Page-Level Permission Check
|
||||||
|
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// app/admin/page.tsx
|
||||||
|
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { getUserSession } from "@/lib/auth";
|
||||||
|
|
||||||
|
export default async function AdminPage() {
|
||||||
|
const session = await getUserSession();
|
||||||
|
|
||||||
|
if (!session || session.user.role !== "admin") {
|
||||||
|
redirect("/"); // Or show an error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protected content here
|
||||||
|
return <div>Welcome, Admin!</div>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧠 Key Reminders
|
||||||
|
|
||||||
|
* Put permission guards in every restricted page.tsx.
|
||||||
|
* Never assume layouts are secure for guarding data.
|
||||||
|
* Validate users before any sensitive queries or rendering.
|
||||||
|
|
||||||
|
|
||||||
|
## Avoid using Dayjs if you don’t need to be strictly tz aware.
|
||||||
|
|
||||||
|
When doing logic like Dayjs.startOf(".."), you can instead use date-fns' `startOfMonth(dateObj)` / `endOfDay(dateObj)`;
|
||||||
|
When doing logic that depends on Browser locale, use i18n.language (prefer to deconstruct) like: `const { i18n: { language } } = useLocale();`, in combination with built-in Intl.
|
||||||
|
|
||||||
|
Note that with Date, you’re dealing with System time, so it’s not suited to everywhere (such as in the Booker, where instead we’ll likely migrate to Temporal) - but in most cases the above are suitable.
|
||||||
|
|
||||||
|
The main reason for doing so is that Dayjs uses a useful, but highly risky plugin system, which has led us to create `@calcom/dayjs` - this is heavy however, because it pre-loads ALL plugins, including locale handling. It’s a non-ideal solution to a problem that unfortunately exists due to Dayjs.
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"access": "public",
|
"access": "public",
|
||||||
"baseBranch": "main",
|
"baseBranch": "main",
|
||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": ["@calcom/platform-libraries"],
|
"ignore": ["@calcom/platform-libraries", "@calcom/api-v2"],
|
||||||
"privatePackages": {
|
"privatePackages": {
|
||||||
"version": false,
|
"version": false,
|
||||||
"tag": false
|
"tag": false
|
||||||
|
|||||||
@@ -0,0 +1,187 @@
|
|||||||
|
language: en-US
|
||||||
|
tone_instructions: ''
|
||||||
|
early_access: false
|
||||||
|
enable_free_tier: true
|
||||||
|
reviews:
|
||||||
|
profile: chill
|
||||||
|
request_changes_workflow: false
|
||||||
|
high_level_summary: false
|
||||||
|
high_level_summary_placeholder: '@coderabbitai summary'
|
||||||
|
high_level_summary_in_walkthrough: false
|
||||||
|
auto_title_placeholder: '@coderabbitai'
|
||||||
|
auto_title_instructions: ''
|
||||||
|
review_status: true
|
||||||
|
commit_status: true
|
||||||
|
fail_commit_status: false
|
||||||
|
collapse_walkthrough: false
|
||||||
|
changed_files_summary: false
|
||||||
|
sequence_diagrams: false
|
||||||
|
estimate_code_review_effort: false
|
||||||
|
assess_linked_issues: true
|
||||||
|
related_issues: false
|
||||||
|
related_prs: true
|
||||||
|
suggested_labels: false
|
||||||
|
auto_apply_labels: false
|
||||||
|
suggested_reviewers: false
|
||||||
|
auto_assign_reviewers: false
|
||||||
|
poem: false
|
||||||
|
labeling_instructions: []
|
||||||
|
path_filters:
|
||||||
|
- '!**/node_modules/**'
|
||||||
|
- '!**/dist/**'
|
||||||
|
- '!**/build/**'
|
||||||
|
- '!**/.next/**'
|
||||||
|
path_instructions:
|
||||||
|
- path: "**/*.{ts,tsx,js,jsx}"
|
||||||
|
instructions: "Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module."
|
||||||
|
abort_on_close: true
|
||||||
|
disable_cache: false
|
||||||
|
auto_review:
|
||||||
|
enabled: true
|
||||||
|
auto_incremental_review: true
|
||||||
|
ignore_title_keywords: []
|
||||||
|
labels: []
|
||||||
|
drafts: false
|
||||||
|
base_branches: []
|
||||||
|
finishing_touches:
|
||||||
|
docstrings:
|
||||||
|
enabled: true
|
||||||
|
unit_tests:
|
||||||
|
enabled: true
|
||||||
|
pre_merge_checks:
|
||||||
|
docstrings:
|
||||||
|
mode: warning
|
||||||
|
threshold: 80
|
||||||
|
title:
|
||||||
|
mode: warning
|
||||||
|
requirements: ''
|
||||||
|
description:
|
||||||
|
mode: warning
|
||||||
|
issue_assessment:
|
||||||
|
mode: warning
|
||||||
|
tools:
|
||||||
|
ast-grep:
|
||||||
|
rule_dirs: []
|
||||||
|
util_dirs: []
|
||||||
|
essential_rules: true
|
||||||
|
packages: []
|
||||||
|
shellcheck:
|
||||||
|
enabled: true
|
||||||
|
ruff:
|
||||||
|
enabled: true
|
||||||
|
markdownlint:
|
||||||
|
enabled: true
|
||||||
|
github-checks:
|
||||||
|
enabled: true
|
||||||
|
timeout_ms: 180000
|
||||||
|
languagetool:
|
||||||
|
enabled: true
|
||||||
|
enabled_rules: []
|
||||||
|
disabled_rules: []
|
||||||
|
enabled_categories: []
|
||||||
|
disabled_categories: []
|
||||||
|
enabled_only: false
|
||||||
|
level: default
|
||||||
|
biome:
|
||||||
|
enabled: true
|
||||||
|
hadolint:
|
||||||
|
enabled: true
|
||||||
|
swiftlint:
|
||||||
|
enabled: true
|
||||||
|
phpstan:
|
||||||
|
enabled: true
|
||||||
|
level: default
|
||||||
|
phpmd:
|
||||||
|
enabled: true
|
||||||
|
phpcs:
|
||||||
|
enabled: true
|
||||||
|
golangci-lint:
|
||||||
|
enabled: true
|
||||||
|
yamllint:
|
||||||
|
enabled: true
|
||||||
|
gitleaks:
|
||||||
|
enabled: true
|
||||||
|
checkov:
|
||||||
|
enabled: true
|
||||||
|
detekt:
|
||||||
|
enabled: true
|
||||||
|
eslint:
|
||||||
|
enabled: true
|
||||||
|
flake8:
|
||||||
|
enabled: true
|
||||||
|
rubocop:
|
||||||
|
enabled: true
|
||||||
|
buf:
|
||||||
|
enabled: true
|
||||||
|
regal:
|
||||||
|
enabled: true
|
||||||
|
actionlint:
|
||||||
|
enabled: true
|
||||||
|
pmd:
|
||||||
|
enabled: true
|
||||||
|
cppcheck:
|
||||||
|
enabled: true
|
||||||
|
semgrep:
|
||||||
|
enabled: true
|
||||||
|
circleci:
|
||||||
|
enabled: true
|
||||||
|
clippy:
|
||||||
|
enabled: true
|
||||||
|
sqlfluff:
|
||||||
|
enabled: true
|
||||||
|
prismaLint:
|
||||||
|
enabled: true
|
||||||
|
pylint:
|
||||||
|
enabled: true
|
||||||
|
oxc:
|
||||||
|
enabled: true
|
||||||
|
shopifyThemeCheck:
|
||||||
|
enabled: true
|
||||||
|
luacheck:
|
||||||
|
enabled: true
|
||||||
|
brakeman:
|
||||||
|
enabled: true
|
||||||
|
dotenvLint:
|
||||||
|
enabled: true
|
||||||
|
htmlhint:
|
||||||
|
enabled: true
|
||||||
|
checkmake:
|
||||||
|
enabled: true
|
||||||
|
osvScanner:
|
||||||
|
enabled: true
|
||||||
|
chat:
|
||||||
|
art: true
|
||||||
|
auto_reply: true
|
||||||
|
integrations:
|
||||||
|
jira:
|
||||||
|
usage: auto
|
||||||
|
linear:
|
||||||
|
usage: auto
|
||||||
|
knowledge_base:
|
||||||
|
opt_out: false
|
||||||
|
web_search:
|
||||||
|
enabled: true
|
||||||
|
code_guidelines:
|
||||||
|
enabled: true
|
||||||
|
filePatterns: []
|
||||||
|
learnings:
|
||||||
|
scope: auto
|
||||||
|
issues:
|
||||||
|
scope: auto
|
||||||
|
jira:
|
||||||
|
usage: auto
|
||||||
|
project_keys: []
|
||||||
|
linear:
|
||||||
|
usage: auto
|
||||||
|
team_keys: []
|
||||||
|
pull_requests:
|
||||||
|
scope: auto
|
||||||
|
mcp:
|
||||||
|
usage: auto
|
||||||
|
disabled_servers: []
|
||||||
|
code_generation:
|
||||||
|
docstrings:
|
||||||
|
language: en-US
|
||||||
|
path_instructions: []
|
||||||
|
unit_tests:
|
||||||
|
path_instructions: []
|
||||||
+36
-9
@@ -103,19 +103,14 @@ NEXT_PUBLIC_INTERCOM_APP_ID=
|
|||||||
# Secret to enable Intercom Identity Verification
|
# Secret to enable Intercom Identity Verification
|
||||||
INTERCOM_SECRET=
|
INTERCOM_SECRET=
|
||||||
|
|
||||||
|
# Intercom api token for free users
|
||||||
|
INTERCOM_API_TOKEN=
|
||||||
|
|
||||||
# Posthog Config
|
# Posthog Config
|
||||||
NEXT_PUBLIC_POSTHOG_KEY=
|
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=
|
|
||||||
|
|
||||||
|
|
||||||
# Dub Config
|
# Dub Config
|
||||||
@@ -149,6 +144,12 @@ GOOGLE_WEBHOOK_TOKEN=
|
|||||||
# Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
|
# Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
|
||||||
GOOGLE_WEBHOOK_URL=
|
GOOGLE_WEBHOOK_URL=
|
||||||
|
|
||||||
|
# Token to verify incoming webhooks from Microsoft Calendar
|
||||||
|
MICROSOFT_WEBHOOK_TOKEN=
|
||||||
|
|
||||||
|
# Optional URL to override for tunelling webhooks. Defaults to NEXT_PUBLIC_WEBAPP_URL.
|
||||||
|
MICROSOFT_WEBHOOK_URL=
|
||||||
|
|
||||||
# Inbox to send user feedback
|
# Inbox to send user feedback
|
||||||
SEND_FEEDBACK_EMAIL=
|
SEND_FEEDBACK_EMAIL=
|
||||||
|
|
||||||
@@ -208,6 +209,7 @@ NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0
|
|||||||
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
|
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
|
||||||
STRIPE_TEAM_MONTHLY_PRICE_ID=
|
STRIPE_TEAM_MONTHLY_PRICE_ID=
|
||||||
NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID=
|
NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID=
|
||||||
|
ORG_MONTHLY_CREDITS=
|
||||||
STRIPE_TEAM_PRODUCT_ID=
|
STRIPE_TEAM_PRODUCT_ID=
|
||||||
# It is a price ID in the product with id STRIPE_ORG_PRODUCT_ID
|
# It is a price ID in the product with id STRIPE_ORG_PRODUCT_ID
|
||||||
STRIPE_ORG_MONTHLY_PRICE_ID=
|
STRIPE_ORG_MONTHLY_PRICE_ID=
|
||||||
@@ -265,6 +267,9 @@ NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
|||||||
NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=
|
NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER=
|
||||||
CLOUDFLARE_TURNSTILE_SECRET=
|
CLOUDFLARE_TURNSTILE_SECRET=
|
||||||
|
|
||||||
|
# 0 = false, 1=true
|
||||||
|
NEXT_PUBLIC_VERCEL_USE_BOTID_IN_BOOKER=
|
||||||
|
|
||||||
# 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
|
||||||
NEXT_PUBLIC_TEAM_IMPERSONATION=false
|
NEXT_PUBLIC_TEAM_IMPERSONATION=false
|
||||||
|
|
||||||
@@ -383,9 +388,27 @@ TASKER_ENABLE_EMAILS=0
|
|||||||
# Ratelimiting via unkey
|
# Ratelimiting via unkey
|
||||||
UNKEY_ROOT_KEY=
|
UNKEY_ROOT_KEY=
|
||||||
|
|
||||||
# Used for Cal.ai Enterprise Voice AI Agents
|
# Used for Cal.ai Voice AI Agents
|
||||||
# https://retellai.com
|
# https://retellai.com
|
||||||
RETELL_AI_KEY=
|
RETELL_AI_KEY=
|
||||||
|
# Local testing overrides for Retell AI
|
||||||
|
RETELL_AI_TEST_MODE=false
|
||||||
|
# JSON mapping of local to production event type IDs (e.g. {"50": 747280} without any string)
|
||||||
|
RETELL_AI_TEST_EVENT_TYPE_MAP=
|
||||||
|
RETELL_AI_TEST_CAL_API_KEY=
|
||||||
|
|
||||||
|
# Used for buying phone number for cal ai voice agent
|
||||||
|
STRIPE_PHONE_NUMBER_MONTHLY_PRICE_ID=
|
||||||
|
|
||||||
|
|
||||||
|
CAL_AI_CALL_RATE_PER_MINUTE=0.29
|
||||||
|
|
||||||
|
|
||||||
|
STRIPE_WEBHOOK_SECRET_BILLING=
|
||||||
|
|
||||||
|
|
||||||
|
# Price for buying a phone number for cal.ai voice agent (Default is 5)
|
||||||
|
NEXT_PUBLIC_CAL_AI_PHONE_NUMBER_MONTHLY_PRICE=
|
||||||
|
|
||||||
# Used for the huddle01 integration
|
# Used for the huddle01 integration
|
||||||
HUDDLE01_API_TOKEN=
|
HUDDLE01_API_TOKEN=
|
||||||
@@ -459,3 +482,7 @@ DATABASE_CHUNK_SIZE=
|
|||||||
# Service Account Encryption Key for encrypting/decrypting service account keys
|
# Service Account Encryption Key for encrypting/decrypting service account keys
|
||||||
# You can use: `openssl rand -base64 24` to generate one
|
# You can use: `openssl rand -base64 24` to generate one
|
||||||
CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY=
|
CALCOM_SERVICE_ACCOUNT_ENCRYPTION_KEY=
|
||||||
|
|
||||||
|
SALESFORCE_GRAPHQL_DELAY_MS=500
|
||||||
|
SALESFORCE_GRAPHQL_MAX_DELAY_MS=2000
|
||||||
|
SALESFORCE_GRAPHQL_MAX_RETRIES=3
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
node_modules
|
|
||||||
apps/api/v2/dist
|
|
||||||
packages/platform/**/dist/*
|
|
||||||
**/**/node_modules
|
|
||||||
**/**/.next
|
|
||||||
**/**/public
|
|
||||||
packages/prisma/zod
|
|
||||||
apps/web/public/embed
|
|
||||||
packages/ui/components/icon/dynamicIconImports.tsx
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
module.exports = require("./packages/config/eslint-preset");
|
|
||||||
+13
-8
@@ -1,11 +1,15 @@
|
|||||||
/**/package.json @calcom/Foundation
|
/**/package.json @calcom/Foundation
|
||||||
/apps/api/**/* @calcom/Platform @calcom/Foundation
|
/apps/api/**/* @calcom/API
|
||||||
/apps/ui-playground/**/* @calcom/Consumer @calcom/Foundation
|
/apps/ui-playground/**/* @calcom/Consumer @calcom/Foundation
|
||||||
/apps/web/**/layout.tsx @calcom/Consumer @calcom/Foundation
|
/apps/web/**/layout.tsx @calcom/Consumer @calcom/Foundation
|
||||||
/apps/web/lib/**/* @calcom/Foundation
|
/apps/web/lib/csp.ts @calcom/Foundation
|
||||||
|
/apps/web/lib/buildNonce.ts @calcom/Foundation
|
||||||
|
/apps/web/lib/daily-webhook/**/* @calcom/Foundation
|
||||||
|
/apps/web/lib/core/**/* @calcom/Foundation
|
||||||
|
/apps/web/lib/booking.ts @calcom/Foundation
|
||||||
|
/apps/web/lib/handleOrgRedirect.ts @calcom/Foundation
|
||||||
/apps/web/middleware.ts @calcom/Foundation
|
/apps/web/middleware.ts @calcom/Foundation
|
||||||
/deploy/**/* @calcom/Foundation
|
/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
|
||||||
@@ -18,14 +22,15 @@
|
|||||||
/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/embeds/**/* @calcom/Foundation
|
/packages/embeds/**/* @calcom/Foundation
|
||||||
|
/packages/features/auth/lib/next-auth-options.ts @calcom/Foundation
|
||||||
/packages/features/bookings/lib/**/* @calcom/Foundation
|
/packages/features/bookings/lib/**/* @calcom/Foundation
|
||||||
/packages/lib/getAggregatedAvailability.ts @calcom/Foundation
|
/packages/features/availability/lib/getAggregatedAvailability/getAggregatedAvailability.ts @calcom/Foundation
|
||||||
/packages/lib/getUserAvailability.ts @calcom/Foundation
|
/packages/features/availability/lib/getUserAvailability.ts @calcom/Foundation
|
||||||
/packages/lib/server/getLuckyUser.ts @calcom/Foundation
|
/packages/features/bookings/lib/getLuckyUser.ts @calcom/Foundation
|
||||||
/packages/lib/slots.ts @calcom/Foundation
|
/packages/features/schedules/lib/slots.ts @calcom/Foundation
|
||||||
/packages/platform/atoms/**/*WebWrapper.tsx @calcom/Consumer
|
/packages/platform/atoms/**/*WebWrapper.tsx @calcom/Consumer
|
||||||
/packages/platform/**/* @calcom/Platform @calcom/Foundation
|
/packages/platform/**/* @calcom/Platform @calcom/Foundation
|
||||||
/packages/prisma/**/* @calcom/DBA
|
/packages/prisma/**/* @calcom/Foundation
|
||||||
/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @calcom/Foundation
|
/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @calcom/Foundation
|
||||||
/packages/trpc/server/routers/viewer/bookings/get.handler.ts @calcom/Foundation
|
/packages/trpc/server/routers/viewer/bookings/get.handler.ts @calcom/Foundation
|
||||||
/packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation
|
/packages/trpc/server/routers/viewer/slots/**/* @calcom/Foundation
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
working-directory: docs
|
working-directory: docs
|
||||||
run: |
|
run: |
|
||||||
export NODE_OPTIONS="--max_old_space_size=8192"
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
npm install -g mintlify@4.0.436
|
npm install -g mintlify@4.2.87
|
||||||
mintlify dev &
|
mintlify dev &
|
||||||
sleep 5 # Let it run for 5 seconds
|
sleep 5 # Let it run for 5 seconds
|
||||||
kill $!
|
kill $!
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: E2E
|
name: Check breaking changes and run E2E
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
@@ -22,6 +22,11 @@ env:
|
|||||||
STRIPE_API_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
STRIPE_API_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SLOTS_CACHE_TTL: ${{ secrets.CI_SLOTS_CACHE_TTL }}
|
||||||
|
NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${{ secrets.NEXT_PUBLIC_VAPID_PUBLIC_KEY }}
|
||||||
|
VAPID_PRIVATE_KEY: ${{ secrets.VAPID_PRIVATE_KEY }}
|
||||||
|
JWT_SECRET: ${{ secrets.CI_JWT_SECRET }}
|
||||||
|
NODE_ENV: ${{ vars.CI_NODE_ENV }}
|
||||||
jobs:
|
jobs:
|
||||||
e2e:
|
e2e:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -67,13 +72,26 @@ jobs:
|
|||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
|
|
||||||
|
- name: Generate Swagger
|
||||||
|
working-directory: apps/api/v2
|
||||||
|
run: yarn generate-swagger
|
||||||
|
|
||||||
|
- name: Check breaking changes
|
||||||
|
uses: oasdiff/oasdiff-action/breaking@main
|
||||||
|
with:
|
||||||
|
base: https://raw.githubusercontent.com/calcom/cal.com/refs/heads/main/docs/api-reference/v2/openapi.json
|
||||||
|
revision: docs/api-reference/v2/openapi.json
|
||||||
|
fail-on: ERR
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
working-directory: apps/api/v2
|
working-directory: apps/api/v2
|
||||||
run: |
|
run: |
|
||||||
yarn test:e2e
|
yarn workspace @calcom/platform-libraries build && yarn test:e2e
|
||||||
EXIT_CODE=$?
|
EXIT_CODE=$?
|
||||||
echo "yarn test:e2e command exit code: $EXIT_CODE"
|
echo "yarn workspace @calcom/platform-libraries build && yarn test:e2e command exit code: $EXIT_CODE"
|
||||||
exit $EXIT_CODE
|
exit $EXIT_CODE
|
||||||
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
name: E2E Atoms
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
permissions:
|
||||||
|
actions: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=8096
|
||||||
|
ATOMS_E2E_OAUTH_CLIENT_ID: ${{ secrets.ATOMS_E2E_OAUTH_CLIENT_ID }}
|
||||||
|
ATOMS_E2E_OAUTH_CLIENT_SECRET: ${{ secrets.ATOMS_E2E_OAUTH_CLIENT_SECRET }}
|
||||||
|
ATOMS_E2E_API_URL: ${{ secrets.ATOMS_E2E_API_URL }}
|
||||||
|
ATOMS_E2E_ORG_ID: ${{ secrets.ATOMS_E2E_ORG_ID }}
|
||||||
|
ATOMS_E2E_OAUTH_CLIENT_ID_BOOKER_EMBED: ${{ secrets.ATOMS_E2E_OAUTH_CLIENT_ID_BOOKER_EMBED }}
|
||||||
|
ATOMS_E2E_APPLE_ID: ${{ secrets.ATOMS_E2E_APPLE_ID }}
|
||||||
|
ATOMS_E2E_APPLE_CONNECT_APP_SPECIFIC_PASSCODE: ${{ secrets.ATOMS_E2E_APPLE_CONNECT_APP_SPECIFIC_PASSCODE }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
e2e-atoms:
|
||||||
|
timeout-minutes: 15
|
||||||
|
name: E2E Atoms
|
||||||
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
- uses: ./.github/actions/yarn-install
|
||||||
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
|
- name: Run E2E Atoms Tests
|
||||||
|
working-directory: packages/platform/examples/base
|
||||||
|
run: yarn test:e2e
|
||||||
|
- name: Upload Test Results
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: e2e-atoms-test-results
|
||||||
|
path: test-results
|
||||||
|
retention-days: 7
|
||||||
|
- name: Upload Playwright Report
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: e2e-atoms-playwright-report
|
||||||
|
path: playwright-report
|
||||||
|
retention-days: 7
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||||
organization-name: calcom
|
organization-name: calcom
|
||||||
ignore-labels: "admin, app-store, ai, authentication, automated-testing, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, dba, devops, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
ignore-labels: "admin, app-store, ai, api, authentication, automated-testing, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, dba, devops, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, self-hosted, sre, teams, webhooks, workflows, zapier"
|
||||||
apply-labels-from-issue:
|
apply-labels-from-issue:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
name: Performance Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
base_url:
|
||||||
|
description: 'Base URL for performance tests'
|
||||||
|
required: false
|
||||||
|
default: 'https://cal.dev'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
performance-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
BASE_URL: ${{ github.event.inputs.base_url || 'https://cal.dev' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup K6
|
||||||
|
uses: grafana/setup-k6-action@v1
|
||||||
|
with:
|
||||||
|
k6-version: '1.0.0'
|
||||||
|
|
||||||
|
- name: Run Smoke Tests
|
||||||
|
uses: grafana/run-k6-action@v1
|
||||||
|
env:
|
||||||
|
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
|
||||||
|
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
|
||||||
|
with:
|
||||||
|
path: tests/performance/smoke/*.js
|
||||||
|
cloud-run-locally: true
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
- name: Run Load Tests
|
||||||
|
uses: grafana/run-k6-action@v1
|
||||||
|
env:
|
||||||
|
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
|
||||||
|
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
|
||||||
|
with:
|
||||||
|
path: tests/performance/load/*.js
|
||||||
|
cloud-run-locally: true
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
- name: Run Stress Tests
|
||||||
|
uses: grafana/run-k6-action@v1
|
||||||
|
env:
|
||||||
|
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
|
||||||
|
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
|
||||||
|
with:
|
||||||
|
path: tests/performance/stress/*.js
|
||||||
|
cloud-run-locally: true
|
||||||
|
fail-fast: true
|
||||||
|
|
||||||
|
- name: Run Spike Tests
|
||||||
|
uses: grafana/run-k6-action@v1
|
||||||
|
env:
|
||||||
|
K6_CLOUD_TOKEN: ${{ secrets.K6_CLOUD_TOKEN }}
|
||||||
|
K6_CLOUD_PROJECT_ID: ${{ secrets.K6_CLOUD_PROJECT_ID }}
|
||||||
|
with:
|
||||||
|
path: tests/performance/spike/*.js
|
||||||
|
cloud-run-locally: true
|
||||||
|
fail-fast: true
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
has-files-requiring-all-checks:
|
has-files-requiring-all-checks:
|
||||||
- "!(**.md|.github/CODEOWNERS|docs/**|help/**|apps/web/public/static/locales/**/common.json)"
|
- "!(**.md|**.mdx|.github/CODEOWNERS|docs/**|help/**|apps/web/public/static/locales/**/common.json|i18n.lock)"
|
||||||
- name: Get Latest Commit SHA
|
- name: Get Latest Commit SHA
|
||||||
id: get_sha
|
id: get_sha
|
||||||
run: |
|
run: |
|
||||||
@@ -248,7 +248,7 @@ jobs:
|
|||||||
e2e-api-v2,
|
e2e-api-v2,
|
||||||
e2e-embed,
|
e2e-embed,
|
||||||
e2e-embed-react,
|
e2e-embed-react,
|
||||||
e2e-app-store,
|
e2e-app-store
|
||||||
]
|
]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-2vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ jobs:
|
|||||||
working-directory: apps/api/v2
|
working-directory: apps/api/v2
|
||||||
run: |
|
run: |
|
||||||
export NODE_OPTIONS="--max_old_space_size=8192"
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
yarn test
|
yarn workspace @calcom/platform-libraries build && yarn test
|
||||||
+6
-1
@@ -66,7 +66,8 @@ tsconfig.tsbuildinfo
|
|||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
# Prisma generated files
|
# Prisma generated files
|
||||||
packages/prisma/zod/*.ts
|
packages/prisma/client/*
|
||||||
|
packages/prisma/zod/**/*.ts
|
||||||
packages/prisma/enums
|
packages/prisma/enums
|
||||||
|
|
||||||
# Builds
|
# Builds
|
||||||
@@ -97,3 +98,7 @@ apps/auth
|
|||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
i18n.cache
|
i18n.cache
|
||||||
|
packages/**/.yarn/ci-cache/
|
||||||
|
|
||||||
|
# AI
|
||||||
|
.claude
|
||||||
@@ -16,5 +16,4 @@ public
|
|||||||
packages/prisma/zod
|
packages/prisma/zod
|
||||||
packages/prisma/enums
|
packages/prisma/enums
|
||||||
apps/web/public/embed
|
apps/web/public/embed
|
||||||
apps/api/v2/swagger/documentation.json
|
|
||||||
packages/ui/components/icon/dynamicIconImports.tsx
|
packages/ui/components/icon/dynamicIconImports.tsx
|
||||||
Vendored
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.formatOnSave": false,
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
|
|||||||
Vendored
+2
-2
@@ -724,7 +724,7 @@ ${we.map(pe=>`Not found: ${lo(pe)}
|
|||||||
To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why <package>\` to get more information as to who depends on them.
|
To understand the dependency tree requiring vulnerable packages, check the raw report with the \`--json\` flag or use \`yarn why <package>\` to get more information as to who depends on them.
|
||||||
`,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"],["Exclude certain packages","yarn npm audit --exclude package1 --exclude package2"],["Ignore specific advisories","yarn npm audit --ignore 1234567 --ignore 7654321"]]});var TM=Pe(Xr()),OM=J("util"),Yu=class extends De{constructor(){super(...arguments);this.fields=z.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"});this.json=z.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.packages=z.Rest()}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),{project:i}=await je.find(t,this.context.cwd),n=typeof this.fields<"u"?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,s=[],o=!1,a=await Ge.start({configuration:t,includeFooter:!1,json:this.json,stdout:this.context.stdout},async l=>{for(let c of this.packages){let u;if(c==="."){let L=i.topLevelWorkspace;if(!L.manifest.name)throw new be(`Missing ${ee.pretty(t,"name",ee.Type.CODE)} field in ${K.fromPortablePath(x.join(L.cwd,xt.manifest))}`);u=P.makeDescriptor(L.manifest.name,"unknown")}else u=P.parseDescriptor(c);let g=Mt.getIdentUrl(u),f=MM(await Mt.get(g,{configuration:t,ident:u,jsonResponse:!0,customErrorMessage:Mt.customPackageError})),h=Object.keys(f.versions).sort(TM.default.compareLoose),C=f["dist-tags"].latest||h[h.length-1],y=vt.validRange(u.range);if(y){let L=TM.default.maxSatisfying(h,y);L!==null?C=L:(l.reportWarning(0,`Unmet range ${P.prettyRange(t,u.range)}; falling back to the latest version`),o=!0)}else Object.prototype.hasOwnProperty.call(f["dist-tags"],u.range)?C=f["dist-tags"][u.range]:u.range!=="unknown"&&(l.reportWarning(0,`Unknown tag ${P.prettyRange(t,u.range)}; falling back to the latest version`),o=!0);let B=f.versions[C],v={...f,...B,version:C,versions:h},D;if(n!==null){D={};for(let L of n){let H=v[L];if(typeof H<"u")D[L]=H;else{l.reportWarning(1,`The ${ee.pretty(t,L,ee.Type.CODE)} field doesn't exist inside ${P.prettyIdent(t,u)}'s information`),o=!0;continue}}}else this.json||(delete v.dist,delete v.readme,delete v.users),D=v;l.reportJson(D),this.json||s.push(D)}});OM.inspect.styles.name="cyan";for(let l of s)(l!==s[0]||o)&&this.context.stdout.write(`
|
`,examples:[["Checks for known security issues with the installed packages. The output is a list of known issues.","yarn npm audit"],["Audit dependencies in all workspaces","yarn npm audit --all"],["Limit auditing to `dependencies` (excludes `devDependencies`)","yarn npm audit --environment production"],["Show audit report as valid JSON","yarn npm audit --json"],["Audit all direct and transitive dependencies","yarn npm audit --recursive"],["Output moderate (or more severe) vulnerabilities","yarn npm audit --severity moderate"],["Exclude certain packages","yarn npm audit --exclude package1 --exclude package2"],["Ignore specific advisories","yarn npm audit --ignore 1234567 --ignore 7654321"]]});var TM=Pe(Xr()),OM=J("util"),Yu=class extends De{constructor(){super(...arguments);this.fields=z.String("-f,--fields",{description:"A comma-separated list of manifest fields that should be displayed"});this.json=z.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.packages=z.Rest()}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),{project:i}=await je.find(t,this.context.cwd),n=typeof this.fields<"u"?new Set(["name",...this.fields.split(/\s*,\s*/)]):null,s=[],o=!1,a=await Ge.start({configuration:t,includeFooter:!1,json:this.json,stdout:this.context.stdout},async l=>{for(let c of this.packages){let u;if(c==="."){let L=i.topLevelWorkspace;if(!L.manifest.name)throw new be(`Missing ${ee.pretty(t,"name",ee.Type.CODE)} field in ${K.fromPortablePath(x.join(L.cwd,xt.manifest))}`);u=P.makeDescriptor(L.manifest.name,"unknown")}else u=P.parseDescriptor(c);let g=Mt.getIdentUrl(u),f=MM(await Mt.get(g,{configuration:t,ident:u,jsonResponse:!0,customErrorMessage:Mt.customPackageError})),h=Object.keys(f.versions).sort(TM.default.compareLoose),C=f["dist-tags"].latest||h[h.length-1],y=vt.validRange(u.range);if(y){let L=TM.default.maxSatisfying(h,y);L!==null?C=L:(l.reportWarning(0,`Unmet range ${P.prettyRange(t,u.range)}; falling back to the latest version`),o=!0)}else Object.prototype.hasOwnProperty.call(f["dist-tags"],u.range)?C=f["dist-tags"][u.range]:u.range!=="unknown"&&(l.reportWarning(0,`Unknown tag ${P.prettyRange(t,u.range)}; falling back to the latest version`),o=!0);let B=f.versions[C],v={...f,...B,version:C,versions:h},D;if(n!==null){D={};for(let L of n){let H=v[L];if(typeof H<"u")D[L]=H;else{l.reportWarning(1,`The ${ee.pretty(t,L,ee.Type.CODE)} field doesn't exist inside ${P.prettyIdent(t,u)}'s information`),o=!0;continue}}}else this.json||(delete v.dist,delete v.readme,delete v.users),D=v;l.reportJson(D),this.json||s.push(D)}});OM.inspect.styles.name="cyan";for(let l of s)(l!==s[0]||o)&&this.context.stdout.write(`
|
||||||
`),this.context.stdout.write(`${(0,OM.inspect)(l,{depth:1/0,colors:!0,compact:!1})}
|
`),this.context.stdout.write(`${(0,OM.inspect)(l,{depth:1/0,colors:!0,compact:!1})}
|
||||||
`);return a.exitCode()}};Yu.paths=[["npm","info"]],Yu.usage=ve.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command fetches information about a package from the npm registry and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@<range>` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react@16.12.0","yarn npm info react@16.12.0"],["Show all available information about react@next","yarn npm info react@next"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]});function MM(r){if(Array.isArray(r)){let e=[];for(let t of r)t=MM(t),t&&e.push(t);return e}else if(typeof r=="object"&&r!==null){let e={};for(let t of Object.keys(r)){if(t.startsWith("_"))continue;let i=MM(r[t]);i&&(e[t]=i)}return e}else return r||null}var sue=Pe(Pm()),ju=class extends De{constructor(){super(...arguments);this.scope=z.String("-s,--scope",{description:"Login to the registry configured for a given scope"});this.publish=z.Boolean("--publish",!1,{description:"Login to the publish registry"})}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),i=await Qb({configuration:t,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ge.start({configuration:t,stdout:this.context.stdout,includeFooter:!1},async s=>{let o=await zVe({configuration:t,registry:i,report:s,stdin:this.context.stdin,stdout:this.context.stdout}),a=`/-/user/org.couchdb.user:${encodeURIComponent(o.name)}`,l=await Mt.put(a,o,{attemptedAs:o.name,configuration:t,registry:i,jsonResponse:!0,authType:Mt.AuthType.NO_AUTH});return await WVe(i,l.token,{configuration:t,scope:this.scope}),s.reportInfo(0,"Successfully logged in")})).exitCode()}};ju.paths=[["npm","login"]],ju.usage=ve.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--scope` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --scope my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]});async function Qb({scope:r,publish:e,configuration:t,cwd:i}){return r&&e?or.getScopeRegistry(r,{configuration:t,type:or.RegistryType.PUBLISH_REGISTRY}):r?or.getScopeRegistry(r,{configuration:t}):e?or.getPublishRegistry((await Ph(t,i)).manifest,{configuration:t}):or.getDefaultRegistry({configuration:t})}async function WVe(r,e,{configuration:t,scope:i}){let n=o=>a=>{let l=Ie.isIndexableObject(a)?a:{},c=l[o],u=Ie.isIndexableObject(c)?c:{};return{...l,[o]:{...u,npmAuthToken:e}}},s=i?{npmScopes:n(i)}:{npmRegistries:n(r)};return await ye.updateHomeConfiguration(s)}async function zVe({configuration:r,registry:e,report:t,stdin:i,stdout:n}){t.reportInfo(0,`Logging in to ${ee.pretty(r,e,ee.Type.URL)}`);let s=!1;if(e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(t.reportInfo(0,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),s=!0),t.reporLine truncated
|
`);return a.exitCode()}};Yu.paths=[["npm","info"]],Yu.usage=ve.Usage({category:"Npm-related commands",description:"show information about a package",details:"\n This command fetches information about a package from the npm registry and prints it in a tree format.\n\n The package does not have to be installed locally, but needs to have been published (in particular, local changes will be ignored even for workspaces).\n\n Append `@<range>` to the package argument to provide information specific to the latest version that satisfies the range or to the corresponding tagged version. If the range is invalid or if there is no version satisfying the range, the command will print a warning and fall back to the latest version.\n\n If the `-f,--fields` option is set, it's a comma-separated list of fields which will be used to only display part of the package information.\n\n By default, this command won't return the `dist`, `readme`, and `users` fields, since they are often very long. To explicitly request those fields, explicitly list them with the `--fields` flag or request the output in JSON mode.\n ",examples:[["Show all available information about react (except the `dist`, `readme`, and `users` fields)","yarn npm info react"],["Show all available information about react as valid JSON (including the `dist`, `readme`, and `users` fields)","yarn npm info react --json"],["Show all available information about react@16.12.0","yarn npm info react@16.12.0"],["Show all available information about react@next","yarn npm info react@next"],["Show the description of react","yarn npm info react --fields description"],["Show all available versions of react","yarn npm info react --fields versions"],["Show the readme of react","yarn npm info react --fields readme"],["Show a few fields of react","yarn npm info react --fields homepage,repository"]]});function MM(r){if(Array.isArray(r)){let e=[];for(let t of r)t=MM(t),t&&e.push(t);return e}else if(typeof r=="object"&&r!==null){let e={};for(let t of Object.keys(r)){if(t.startsWith("_"))continue;let i=MM(r[t]);i&&(e[t]=i)}return e}else return r||null}var sue=Pe(Pm()),ju=class extends De{constructor(){super(...arguments);this.scope=z.String("-s,--filter",{description:"Login to the registry configured for a given scope"});this.publish=z.Boolean("--publish",!1,{description:"Login to the publish registry"})}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),i=await Qb({configuration:t,cwd:this.context.cwd,publish:this.publish,scope:this.scope});return(await Ge.start({configuration:t,stdout:this.context.stdout,includeFooter:!1},async s=>{let o=await zVe({configuration:t,registry:i,report:s,stdin:this.context.stdin,stdout:this.context.stdout}),a=`/-/user/org.couchdb.user:${encodeURIComponent(o.name)}`,l=await Mt.put(a,o,{attemptedAs:o.name,configuration:t,registry:i,jsonResponse:!0,authType:Mt.AuthType.NO_AUTH});return await WVe(i,l.token,{configuration:t,scope:this.scope}),s.reportInfo(0,"Successfully logged in")})).exitCode()}};ju.paths=[["npm","login"]],ju.usage=ve.Usage({category:"Npm-related commands",description:"store new login info to access the npm registry",details:"\n This command will ask you for your username, password, and 2FA One-Time-Password (when it applies). It will then modify your local configuration (in your home folder, never in the project itself) to reference the new tokens thus generated.\n\n Adding the `-s,--filter` flag will cause the authentication to be done against whatever registry is configured for the associated scope (see also `npmScopes`).\n\n Adding the `--publish` flag will cause the authentication to be done against the registry used when publishing the package (see also `publishConfig.registry` and `npmPublishRegistry`).\n ",examples:[["Login to the default registry","yarn npm login"],["Login to the registry linked to the @my-scope registry","yarn npm login --filter my-scope"],["Login to the publish registry for the current package","yarn npm login --publish"]]});async function Qb({scope:r,publish:e,configuration:t,cwd:i}){return r&&e?or.getScopeRegistry(r,{configuration:t,type:or.RegistryType.PUBLISH_REGISTRY}):r?or.getScopeRegistry(r,{configuration:t}):e?or.getPublishRegistry((await Ph(t,i)).manifest,{configuration:t}):or.getDefaultRegistry({configuration:t})}async function WVe(r,e,{configuration:t,scope:i}){let n=o=>a=>{let l=Ie.isIndexableObject(a)?a:{},c=l[o],u=Ie.isIndexableObject(c)?c:{};return{...l,[o]:{...u,npmAuthToken:e}}},s=i?{npmScopes:n(i)}:{npmRegistries:n(r)};return await ye.updateHomeConfiguration(s)}async function zVe({configuration:r,registry:e,report:t,stdin:i,stdout:n}){t.reportInfo(0,`Logging in to ${ee.pretty(r,e,ee.Type.URL)}`);let s=!1;if(e.match(/^https:\/\/npm\.pkg\.github\.com(\/|$)/)&&(t.reportInfo(0,"You seem to be using the GitHub Package Registry. Tokens must be generated with the 'repo', 'write:packages', and 'read:packages' permissions."),s=!0),t.reLine truncated
|
||||||
This command will list all tags of a package from the npm registry.
|
This command will list all tags of a package from the npm registry.
|
||||||
|
|
||||||
If the package is not specified, Yarn will default to the current workspace.
|
If the package is not specified, Yarn will default to the current workspace.
|
||||||
@@ -732,7 +732,7 @@ ${we.map(pe=>`Not found: ${lo(pe)}
|
|||||||
This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten.
|
This command will add a tag to the npm registry for a specific version of a package. If the tag already exists, it will be overwritten.
|
||||||
`,examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]});var Vu=class extends De{constructor(){super(...arguments);this.package=z.String();this.tag=z.String()}async execute(){if(this.tag==="latest")throw new be("The 'latest' tag cannot be removed.");let t=await ye.find(this.context.cwd,this.context.plugins),{project:i,workspace:n}=await je.find(t,this.context.cwd);if(!n)throw new ct(i.cwd,this.context.cwd);let s=P.parseIdent(this.package),o=or.getPublishRegistry(n.manifest,{configuration:t}),a=ee.pretty(t,this.tag,ee.Type.CODE),l=ee.pretty(t,s,ee.Type.IDENT),c=await IE(s,t);if(!Object.prototype.hasOwnProperty.call(c,this.tag))throw new be(`${a} is not a tag of package ${l}`);return(await Ge.start({configuration:t,stdout:this.context.stdout},async g=>{let f=`/-/package${Mt.getIdentUrl(s)}/dist-tags/${encodeURIComponent(this.tag)}`;await Mt.del(f,{configuration:t,registry:o,ident:s,jsonResponse:!0}),g.reportInfo(0,`Tag ${a} removed from package ${l}`)})).exitCode()}};Vu.paths=[["npm","tag","remove"]],Vu.usage=ve.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:`
|
`,examples:[["Add a `beta` tag for version `2.3.4-beta.4` of package `my-pkg`","yarn npm tag add my-pkg@2.3.4-beta.4 beta"]]});var Vu=class extends De{constructor(){super(...arguments);this.package=z.String();this.tag=z.String()}async execute(){if(this.tag==="latest")throw new be("The 'latest' tag cannot be removed.");let t=await ye.find(this.context.cwd,this.context.plugins),{project:i,workspace:n}=await je.find(t,this.context.cwd);if(!n)throw new ct(i.cwd,this.context.cwd);let s=P.parseIdent(this.package),o=or.getPublishRegistry(n.manifest,{configuration:t}),a=ee.pretty(t,this.tag,ee.Type.CODE),l=ee.pretty(t,s,ee.Type.IDENT),c=await IE(s,t);if(!Object.prototype.hasOwnProperty.call(c,this.tag))throw new be(`${a} is not a tag of package ${l}`);return(await Ge.start({configuration:t,stdout:this.context.stdout},async g=>{let f=`/-/package${Mt.getIdentUrl(s)}/dist-tags/${encodeURIComponent(this.tag)}`;await Mt.del(f,{configuration:t,registry:o,ident:s,jsonResponse:!0}),g.reportInfo(0,`Tag ${a} removed from package ${l}`)})).exitCode()}};Vu.paths=[["npm","tag","remove"]],Vu.usage=ve.Usage({category:"Npm-related commands",description:"remove a tag from a package",details:`
|
||||||
This command will remove a tag from a package from the npm registry.
|
This command will remove a tag from a package from the npm registry.
|
||||||
`,examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]});var Xu=class extends De{constructor(){super(...arguments);this.scope=z.String("-s,--scope",{description:"Print username for the registry configured for a given scope"});this.publish=z.Boolean("--publish",!1,{description:"Print username for the publish registry"})}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),i;return this.scope&&this.publish?i=or.getScopeRegistry(this.scope,{configuration:t,type:or.RegistryType.PUBLISH_REGISTRY}):this.scope?i=or.getScopeRegistry(this.scope,{configuration:t}):this.publish?i=or.getPublishRegistry((await Ph(t,this.context.cwd)).manifest,{configuration:t}):i=or.getDefaultRegistry({configuration:t}),(await Ge.start({configuration:t,stdout:this.context.stdout},async s=>{var a,l;let o;try{o=await Mt.get("/-/whoami",{configuration:t,registry:i,authType:Mt.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?P.makeIdent(this.scope,""):void 0})}catch(c){if(((a=c.response)==null?void 0:a.statusCode)===401||((l=c.response)==null?void 0:l.statusCode)===403){s.reportError(41,"Authentication failed - your credentials may have expired");return}else throw c}s.reportInfo(0,o.username)})).exitCode()}};Xu.paths=[["npm","whoami"]],Xu.usage=ve.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--scope`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --scope company"]]});var _Ve={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:"STRING",default:null},npmAuditExcludePackages:{description:"Array of glob patterns of packages to exclude from npm audit",type:"STRING",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:"Array of glob patterns of advisory IDs to exclude from npm audit",type:"STRING",default:[],isArray:!0}},commands:[Gu,Yu,ju,qu,Ju,zu,Wu,Vu,Xu]},ZVe=_Ve;var zM={};ut(zM,{default:()=>h6e,patchUtils:()=>_u});var _u={};ut(_u,{applyPatchFile:()=>Sb,diffFolders:()=>JM,ensureUnpatchedDescriptor:()=>HM,extractPackageToDisk:()=>qM,extractPatchFlags:()=>hue,isParentRequired:()=>jM,loadPatchFiles:()=>QE,makeDescriptor:()=>GM,makeLocator:()=>YM,makePatchHash:()=>WM,parseDescriptor:()=>wE,parseLocator:()=>BE,parsePatchFile:()=>yE});var $Ve=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function Ap(r){return x.relative(Oe.root,x.resolve(Oe.root,K.toPortablePath(r)))}function e6e(r){let e=r.trim().match($Ve);if(!e)throw new Error(`Bad header line: '${r}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var t6e=420,r6e=493;var Aue=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),i6e=r=>({header:e6e(r),parts:[]}),n6e={["@"]:"header",["-"]:"deletion",["+"]:"insertion",[" "]:"context",["\\"]:"pragma",undefined:"context"};function s6e(r){let e=[],t=Aue(),i="parsing header",n=null,s=null;function o(){n&&(s&&(n.parts.push(s),s=null),t.hunks.push(n),n=null)}function a(){o(),e.push(t),t=Aue()}for(let l=0;l<r.length;l++){let c=r[l];if(i==="parsing header")if(c.startsWith("@@"))i="parsing hunks",t.hunks=[],l-=1;else if(c.startsWith("diff --git ")){t&&t.diffLineFromPath&&a();let u=c.match(/^diff --git a\/(.*?) b\/(.*?)\s*$/);if(!u)throw new Error(`Bad diff line: ${c}`);t.diffLineFromPath=u[1],t.diffLineToPath=u[2]}else if(c.startsWith("old mode "))t.oldMode=c.slice(9).trim();else if(c.startsWith("new mode "))t.newMode=c.slice(9).trim();else if(c.startsWith("deleted file mode "))t.deletedFileMode=c.slice(18).trim();else if(c.startsWith("new file mode "))t.newFileMode=c.slice(14).trim();else if(c.startsWith("rename from "))t.renameFrom=c.slice(12).trim();else if(c.startsWith("rename to "))t.renameTo=c.slice(10).trim();else if(c.startsWith("index ")){let u=c.match(/(\w+)\.\.(\w+)/);if(!u)continue;t.beforeHash=u[1],t.afterHash=u[2]}else c.startsWith("semver exclusivity ")?t.semverExclusivity=c.slice(19).trim():c.startsWith("--- ")?t.fromPath=c.slice(6).trim():c.startsWith("+++ ")&&(t.toP
Line truncated
|
`,examples:[["Remove the `beta` tag from package `my-pkg`","yarn npm tag remove my-pkg beta"]]});var Xu=class extends De{constructor(){super(...arguments);this.scope=z.String("-s,--filter",{description:"Print username for the registry configured for a given scope"});this.publish=z.Boolean("--publish",!1,{description:"Print username for the publish registry"})}async execute(){let t=await ye.find(this.context.cwd,this.context.plugins),i;return this.scope&&this.publish?i=or.getScopeRegistry(this.scope,{configuration:t,type:or.RegistryType.PUBLISH_REGISTRY}):this.scope?i=or.getScopeRegistry(this.scope,{configuration:t}):this.publish?i=or.getPublishRegistry((await Ph(t,this.context.cwd)).manifest,{configuration:t}):i=or.getDefaultRegistry({configuration:t}),(await Ge.start({configuration:t,stdout:this.context.stdout},async s=>{var a,l;let o;try{o=await Mt.get("/-/whoami",{configuration:t,registry:i,authType:Mt.AuthType.ALWAYS_AUTH,jsonResponse:!0,ident:this.scope?P.makeIdent(this.scope,""):void 0})}catch(c){if(((a=c.response)==null?void 0:a.statusCode)===401||((l=c.response)==null?void 0:l.statusCode)===403){s.reportError(41,"Authentication failed - your credentials may have expired");return}else throw c}s.reportInfo(0,o.username)})).exitCode()}};Xu.paths=[["npm","whoami"]],Xu.usage=ve.Usage({category:"Npm-related commands",description:"display the name of the authenticated user",details:"\n Print the username associated with the current authentication settings to the standard output.\n\n When using `-s,--filter`, the username printed will be the one that matches the authentication settings of the registry associated with the given scope (those settings can be overriden using the `npmRegistries` map, and the registry associated with the scope is configured via the `npmScopes` map).\n\n When using `--publish`, the registry we'll select will by default be the one used when publishing packages (`publishConfig.registry` or `npmPublishRegistry` if available, otherwise we'll fallback to the regular `npmRegistryServer`).\n ",examples:[["Print username for the default registry","yarn npm whoami"],["Print username for the registry on a given scope","yarn npm whoami --filter company"]]});var _Ve={configuration:{npmPublishAccess:{description:"Default access of the published packages",type:"STRING",default:null},npmAuditExcludePackages:{description:"Array of glob patterns of packages to exclude from npm audit",type:"STRING",default:[],isArray:!0},npmAuditIgnoreAdvisories:{description:"Array of glob patterns of advisory IDs to exclude from npm audit",type:"STRING",default:[],isArray:!0}},commands:[Gu,Yu,ju,qu,Ju,zu,Wu,Vu,Xu]},ZVe=_Ve;var zM={};ut(zM,{default:()=>h6e,patchUtils:()=>_u});var _u={};ut(_u,{applyPatchFile:()=>Sb,diffFolders:()=>JM,ensureUnpatchedDescriptor:()=>HM,extractPackageToDisk:()=>qM,extractPatchFlags:()=>hue,isParentRequired:()=>jM,loadPatchFiles:()=>QE,makeDescriptor:()=>GM,makeLocator:()=>YM,makePatchHash:()=>WM,parseDescriptor:()=>wE,parseLocator:()=>BE,parsePatchFile:()=>yE});var $Ve=/^@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@.*/;function Ap(r){return x.relative(Oe.root,x.resolve(Oe.root,K.toPortablePath(r)))}function e6e(r){let e=r.trim().match($Ve);if(!e)throw new Error(`Bad header line: '${r}'`);return{original:{start:Math.max(Number(e[1]),1),length:Number(e[3]||1)},patched:{start:Math.max(Number(e[4]),1),length:Number(e[6]||1)}}}var t6e=420,r6e=493;var Aue=()=>({semverExclusivity:null,diffLineFromPath:null,diffLineToPath:null,oldMode:null,newMode:null,deletedFileMode:null,newFileMode:null,renameFrom:null,renameTo:null,beforeHash:null,afterHash:null,fromPath:null,toPath:null,hunks:null}),i6e=r=>({header:e6e(r),parts:[]}),n6e={["@"]:"header",["-"]:"deletion",["+"]:"insertion",[" "]:"context",["\\"]:"pragma",undefined:"context"};function s6e(r){let e=[],t=Aue(),i="parsing header",n=null,s=null;function o(){n&&(s&&(n.parts.push(s),s=null),t.hunks.push(n),n=null)}function a(){o(),e.push(t),t=Aue()}for(let l=0;l<r.length;l++){let c=r[l];if(i==="parsing header")if(c.startsWith("@@"))i="parsing hunks",t.hunks=[],l-=1;else if(c.startsWith("diff --git ")){t&&t.diffLineFromPath&&a();let u=c.match(/^diff --git a\/(.*?) b\/(.*?)\s*$/);if(!u)throw new Error(`Bad diff line: ${c}`);t.diffLineFromPath=u[1],t.diffLineToPath=u[2]}else if(c.startsWith("old mode "))t.oldMode=c.slice(9).trim();else if(c.startsWith("new mode "))t.newMode=c.slice(9).trim();else if(c.startsWith("deleted file mode "))t.deletedFileMode=c.slice(18).trim();else if(c.startsWith("new file mode "))t.newFileMode=c.slice(14).trim();else if(c.startsWith("rename from "))t.renameFrom=c.slice(12).trim();else if(c.startsWith("rename to "))t.renameTo=c.slice(10).trim();else if(c.startsWith("index ")){let u=c.match(/(\w+)\.\.(\w+)/);if(!u)continue;t.beforeHash=u[1],t.afterHash=u[2]}else c.startsWith("semver exclusivity ")?t.semverExclusivity=c.slice(19).trim():c.startsWith("--- ")?t.fromPath=c.slice(6).trim():c.startsWith("+++ ")&&(t.
Line truncated
|
||||||
`)+(n.hunk.parts[0].noNewlineAtEndOfFile?"":`
|
`)+(n.hunk.parts[0].noNewlineAtEndOfFile?"":`
|
||||||
`):"";await e.mkdirpPromise(x.dirname(n.path),{chmod:493,utimes:[xr.SAFE_TIME,xr.SAFE_TIME]}),await e.writeFilePromise(n.path,s,{mode:n.mode}),await e.utimesPromise(n.path,xr.SAFE_TIME,xr.SAFE_TIME)}break;case"patch":await cp(e,n.path,async()=>{await c6e(n,{baseFs:e,dryRun:t})});break;case"mode change":{let o=(await e.statPromise(n.path)).mode;if(lue(n.newMode)!==lue(o))continue;await cp(e,n.path,async()=>{await e.chmodPromise(n.path,n.newMode)})}break;default:Ie.assertNever(n);break}}function lue(r){return(r&64)>0}function cue(r){return r.replace(/\s+$/,"")}function l6e(r,e){return cue(r)===cue(e)}async function c6e({hunks:r,path:e},{baseFs:t,dryRun:i=!1}){let n=await t.statSync(e).mode,o=(await t.readFileSync(e,"utf8")).split(/\n/),a=[],l=0,c=0;for(let g of r){let f=Math.max(c,g.header.patched.start+l),h=Math.max(0,f-c),p=Math.max(0,o.length-f-g.header.original.length),C=Math.max(h,p),y=0,B=0,v=null;for(;y<=C;){if(y<=h&&(B=f-y,v=uue(g,o,B),v!==null)){y=-y;break}if(y<=p&&(B=f+y,v=uue(g,o,B),v!==null))break;y+=1}if(v===null)throw new lp(r.indexOf(g),g);a.push(v),l+=y,c=B+g.header.original.length}if(i)return;let u=0;for(let g of a)for(let f of g)switch(f.type){case"splice":{let h=f.index+u;o.splice(h,f.numToDelete,...f.linesToInsert),u+=f.linesToInsert.length-f.numToDelete}break;case"pop":o.pop();break;case"push":o.push(f.line);break;default:Ie.assertNever(f);break}await t.writeFilePromise(e,o.join(`
|
`):"";await e.mkdirpPromise(x.dirname(n.path),{chmod:493,utimes:[xr.SAFE_TIME,xr.SAFE_TIME]}),await e.writeFilePromise(n.path,s,{mode:n.mode}),await e.utimesPromise(n.path,xr.SAFE_TIME,xr.SAFE_TIME)}break;case"patch":await cp(e,n.path,async()=>{await c6e(n,{baseFs:e,dryRun:t})});break;case"mode change":{let o=(await e.statPromise(n.path)).mode;if(lue(n.newMode)!==lue(o))continue;await cp(e,n.path,async()=>{await e.chmodPromise(n.path,n.newMode)})}break;default:Ie.assertNever(n);break}}function lue(r){return(r&64)>0}function cue(r){return r.replace(/\s+$/,"")}function l6e(r,e){return cue(r)===cue(e)}async function c6e({hunks:r,path:e},{baseFs:t,dryRun:i=!1}){let n=await t.statSync(e).mode,o=(await t.readFileSync(e,"utf8")).split(/\n/),a=[],l=0,c=0;for(let g of r){let f=Math.max(c,g.header.patched.start+l),h=Math.max(0,f-c),p=Math.max(0,o.length-f-g.header.original.length),C=Math.max(h,p),y=0,B=0,v=null;for(;y<=C;){if(y<=h&&(B=f-y,v=uue(g,o,B),v!==null)){y=-y;break}if(y<=p&&(B=f+y,v=uue(g,o,B),v!==null))break;y+=1}if(v===null)throw new lp(r.indexOf(g),g);a.push(v),l+=y,c=B+g.header.original.length}if(i)return;let u=0;for(let g of a)for(let f of g)switch(f.type){case"splice":{let h=f.index+u;o.splice(h,f.numToDelete,...f.linesToInsert),u+=f.linesToInsert.length-f.numToDelete}break;case"pop":o.pop();break;case"push":o.push(f.line);break;default:Ie.assertNever(f);break}await t.writeFilePromise(e,o.join(`
|
||||||
`),{mode:n})}function uue(r,e,t){let i=[];for(let n of r.parts)switch(n.type){case"context":case"deletion":{for(let s of n.lines){let o=e[t];if(o==null||!l6e(o,s))return null;t+=1}n.type==="deletion"&&(i.push({type:"splice",index:t-n.lines.length,numToDelete:n.lines.length,linesToInsert:[]}),n.noNewlineAtEndOfFile&&i.push({type:"push",line:""}))}break;case"insertion":i.push({type:"splice",index:t,numToDelete:0,linesToInsert:n.lines}),n.noNewlineAtEndOfFile&&i.push({type:"pop"});break;default:Ie.assertNever(n.type);break}return i}var g6e=/^builtin<([^>]+)>$/;function UM(r,e){let{source:t,selector:i,params:n}=P.parseRange(r);if(t===null)throw new Error("Patch locators must explicitly define their source");let s=i?i.split(/&/).map(c=>K.toPortablePath(c)):[],o=n&&typeof n.locator=="string"?P.parseLocator(n.locator):null,a=n&&typeof n.version=="string"?n.version:null,l=e(t);return{parentLocator:o,sourceItem:l,patchPaths:s,sourceVersion:a}}function wE(r){let{sourceItem:e,...t}=UM(r.range,P.parseDescriptor);return{...t,sourceDescriptor:e}}function BE(r){let{sourceItem:e,...t}=UM(r.reference,P.parseLocator);return{...t,sourceLocator:e}}function HM(r){if(!r.range.startsWith("patch:"))return r;let{sourceItem:e}=UM(r.range,P.parseDescriptor);return e}function gue({parentLocator:r,sourceItem:e,patchPaths:t,sourceVersion:i,patchHash:n},s){let o=r!==null?{locator:P.stringifyLocator(r)}:{},a=typeof i<"u"?{version:i}:{},l=typeof n<"u"?{hash:n}:{};return P.makeRange({protocol:"patch:",source:s(e),selector:t.join("&"),params:{...a,...l,...o}})}function GM(r,{parentLocator:e,sourceDescriptor:t,patchPaths:i}){return P.makeDescriptor(r,gue({parentLocator:e,sourceItem:t,patchPaths:i},P.stringifyDescriptor))}function YM(r,{parentLocator:e,sourcePackage:t,patchPaths:i,patchHash:n}){return P.makeLocator(r,gue({parentLocator:e,sourceItem:t,sourceVersion:t.version,patchPaths:i,patchHash:n},P.stringifyLocator))}function fue({onAbsolute:r,onRelative:e,onBuiltin:t},i){i.startsWith("~")&&(i=i.slice(1));let s=i.match(g6e);return s!==null?t(s[1]):x.isAbsolute(i)?r(i):e(i)}function hue(r){let e=r.startsWith("~");return e&&(r=r.slice(1)),{optional:e}}function jM(r){return fue({onAbsolute:()=>!1,onRelative:()=>!0,onBuiltin:()=>!1},r)}async function QE(r,e,t){let i=r!==null?await t.fetcher.fetch(r,t):null,n=i&&i.localPath?{packageFs:new jt(Oe.root),prefixPath:x.relative(Oe.root,i.localPath)}:i;i&&i!==n&&i.releaseFs&&i.releaseFs();let s=await Ie.releaseAfterUseAsync(async()=>await Promise.all(e.map(async o=>{let a=hue(o),l=await fue({onAbsolute:async()=>await M.readFilePromise(o,"utf8"),onRelative:async()=>{if(n===null)throw new Error("Assertion failed: The parent locator should have been fetched");return await n.packageFs.readFilePromise(x.join(n.prefixPath,o),"utf8")},onBuiltin:async c=>await t.project.configuration.firstHook(u=>u.getBuiltinPatch,t.project,c)},o);return{...a,source:l}})));for(let o of s)typeof o.source=="string"&&(o.source=o.source.replace(/\r\n?/g,`
|
`),{mode:n})}function uue(r,e,t){let i=[];for(let n of r.parts)switch(n.type){case"context":case"deletion":{for(let s of n.lines){let o=e[t];if(o==null||!l6e(o,s))return null;t+=1}n.type==="deletion"&&(i.push({type:"splice",index:t-n.lines.length,numToDelete:n.lines.length,linesToInsert:[]}),n.noNewlineAtEndOfFile&&i.push({type:"push",line:""}))}break;case"insertion":i.push({type:"splice",index:t,numToDelete:0,linesToInsert:n.lines}),n.noNewlineAtEndOfFile&&i.push({type:"pop"});break;default:Ie.assertNever(n.type);break}return i}var g6e=/^builtin<([^>]+)>$/;function UM(r,e){let{source:t,selector:i,params:n}=P.parseRange(r);if(t===null)throw new Error("Patch locators must explicitly define their source");let s=i?i.split(/&/).map(c=>K.toPortablePath(c)):[],o=n&&typeof n.locator=="string"?P.parseLocator(n.locator):null,a=n&&typeof n.version=="string"?n.version:null,l=e(t);return{parentLocator:o,sourceItem:l,patchPaths:s,sourceVersion:a}}function wE(r){let{sourceItem:e,...t}=UM(r.range,P.parseDescriptor);return{...t,sourceDescriptor:e}}function BE(r){let{sourceItem:e,...t}=UM(r.reference,P.parseLocator);return{...t,sourceLocator:e}}function HM(r){if(!r.range.startsWith("patch:"))return r;let{sourceItem:e}=UM(r.range,P.parseDescriptor);return e}function gue({parentLocator:r,sourceItem:e,patchPaths:t,sourceVersion:i,patchHash:n},s){let o=r!==null?{locator:P.stringifyLocator(r)}:{},a=typeof i<"u"?{version:i}:{},l=typeof n<"u"?{hash:n}:{};return P.makeRange({protocol:"patch:",source:s(e),selector:t.join("&"),params:{...a,...l,...o}})}function GM(r,{parentLocator:e,sourceDescriptor:t,patchPaths:i}){return P.makeDescriptor(r,gue({parentLocator:e,sourceItem:t,patchPaths:i},P.stringifyDescriptor))}function YM(r,{parentLocator:e,sourcePackage:t,patchPaths:i,patchHash:n}){return P.makeLocator(r,gue({parentLocator:e,sourceItem:t,sourceVersion:t.version,patchPaths:i,patchHash:n},P.stringifyLocator))}function fue({onAbsolute:r,onRelative:e,onBuiltin:t},i){i.startsWith("~")&&(i=i.slice(1));let s=i.match(g6e);return s!==null?t(s[1]):x.isAbsolute(i)?r(i):e(i)}function hue(r){let e=r.startsWith("~");return e&&(r=r.slice(1)),{optional:e}}function jM(r){return fue({onAbsolute:()=>!1,onRelative:()=>!0,onBuiltin:()=>!1},r)}async function QE(r,e,t){let i=r!==null?await t.fetcher.fetch(r,t):null,n=i&&i.localPath?{packageFs:new jt(Oe.root),prefixPath:x.relative(Oe.root,i.localPath)}:i;i&&i!==n&&i.releaseFs&&i.releaseFs();let s=await Ie.releaseAfterUseAsync(async()=>await Promise.all(e.map(async o=>{let a=hue(o),l=await fue({onAbsolute:async()=>await M.readFilePromise(o,"utf8"),onRelative:async()=>{if(n===null)throw new Error("Assertion failed: The parent locator should have been fetched");return await n.packageFs.readFilePromise(x.join(n.prefixPath,o),"utf8")},onBuiltin:async c=>await t.project.configuration.firstHook(u=>u.getBuiltinPatch,t.project,c)},o);return{...a,source:l}})));for(let o of s)typeof o.source=="string"&&(o.source=o.source.replace(/\r\n?/g,`
|
||||||
|
|||||||
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@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/app-store"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
Whitespace-only changes.
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
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
Whitespace-only changes.
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Whitespace-only changes.
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# Cal.com Development Guide for AI Agents
|
||||||
|
|
||||||
|
**📖 Complete documentation is in the [.agents/](.agents/) directory.**
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Essential Commands
|
||||||
|
|
||||||
|
- `yarn dev` - Start development server
|
||||||
|
- `yarn build` - Build all packages
|
||||||
|
- `yarn lint:fix` - Lint and fix code
|
||||||
|
- `yarn type-check` - TypeScript checking
|
||||||
|
- `yarn test <filename> -- --integrationTestsOnly` - Run integration tests
|
||||||
|
- `yarn e2e <filename> --grep "<testName>"` - Run specific E2E test
|
||||||
|
|
||||||
|
## Tool Preferences
|
||||||
|
|
||||||
|
### Search Tools Priority
|
||||||
|
|
||||||
|
Use tools in this order of preference:
|
||||||
|
|
||||||
|
1. **ast-grep** - For AST-based code searches (if available)
|
||||||
|
2. **rg (ripgrep)** - For fast text searches
|
||||||
|
3. **grep** - As fallback for text searches
|
||||||
|
|
||||||
|
## 📚 Detailed Documentation
|
||||||
|
|
||||||
|
- **[.agents/README.md](.agents/README.md)** - Complete development guide
|
||||||
|
- **[.agents/commands.md](.agents/commands.md)** - All build, test & dev commands
|
||||||
|
- **[.agents/knowledge-base.md](.agents/knowledge-base.md)** - Knowledge base & best practices
|
||||||
@@ -13,5 +13,8 @@
|
|||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"http": "^0.0.1-security",
|
"http": "^0.0.1-security",
|
||||||
"http-proxy-middleware": "^2.0.6"
|
"http-proxy-middleware": "^2.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@calcom/eslint-config": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ There's some settings that you'll need to setup.
|
|||||||
Under Vercel > Your API Project > Settings
|
Under Vercel > Your API Project > Settings
|
||||||
|
|
||||||
In General > Build & Development Settings
|
In General > Build & Development Settings
|
||||||
BUILD COMMAND: `yarn turbo run build --scope=@calcom/api --include-dependencies --no-deps`
|
BUILD COMMAND: `yarn turbo run build --filter=@calcom/api --no-deps`
|
||||||
OUTPUT DIRECTORY: `apps/api/.next`
|
OUTPUT DIRECTORY: `apps/api/.next`
|
||||||
|
|
||||||
In Git > Ignored Build Step
|
In Git > Ignored Build Step
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { nextJsConfig } from "@calcom/eslint-config/next-js";
|
||||||
|
|
||||||
|
export default nextJsConfig;
|
||||||
@@ -1,20 +1,14 @@
|
|||||||
import { captureException as SentryCaptureException } from "@sentry/nextjs";
|
import { captureException as SentryCaptureException } from "@sentry/nextjs";
|
||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { redactError } from "@calcom/lib/redactError";
|
|
||||||
|
|
||||||
export const captureErrors: NextMiddleware = async (_req, res, next) => {
|
export const captureErrors: NextMiddleware = async (_req, res, next) => {
|
||||||
try {
|
try {
|
||||||
// Catch any errors that are thrown in remaining
|
// Catch any errors that are thrown in remaining
|
||||||
// middleware and the API route handler
|
// middleware and the API route handler
|
||||||
await next();
|
await next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
SentryCaptureException(error);
|
SentryCaptureException(error);
|
||||||
const redactedError = redactError(error);
|
res.status(500).json({ message: "Something went wrong" });
|
||||||
if (redactedError instanceof Error) {
|
|
||||||
res.status(400).json({ message: redactedError.message, error: redactedError });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
res.status(400).json({ message: "Something went wrong", error });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4,7 +4,7 @@ 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 { handleAutoLock } from "@calcom/features/ee/api-keys/lib/autoLock";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
|
|||||||
checkRateLimitAndThrowError: vi.fn(),
|
checkRateLimitAndThrowError: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@calcom/lib/autoLock", () => ({
|
vi.mock("@calcom/features/ee/api-keys/lib/autoLock", () => ({
|
||||||
handleAutoLock: vi.fn(),
|
handleAutoLock: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
query: {},
|
query: {},
|
||||||
userId: testUserId,
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(res._getStatusCode()).toBe(401);
|
expect(res._getStatusCode()).toBe(401);
|
||||||
@@ -43,7 +43,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
query: { apiKey: "test-key" },
|
query: { apiKey: "test-key" },
|
||||||
userId: testUserId,
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
|
||||||
limit: 100,
|
limit: 100,
|
||||||
remaining: 99,
|
remaining: 99,
|
||||||
@@ -51,6 +51,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
|
||||||
@@ -74,15 +75,14 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// @ts-ignore
|
|
||||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||||
onRateLimiterResponse(rateLimiterResponse);
|
onRateLimiterResponse(rateLimiterResponse);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
|
expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
|
||||||
@@ -96,10 +96,11 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
query: { apiKey: "test-key" },
|
query: { apiKey: "test-key" },
|
||||||
userId: testUserId,
|
userId: testUserId,
|
||||||
});
|
});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(res._getStatusCode()).toBe(429);
|
expect(res._getStatusCode()).toBe(429);
|
||||||
@@ -121,6 +122,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||||
onRateLimiterResponse(rateLimiterResponse);
|
onRateLimiterResponse(rateLimiterResponse);
|
||||||
@@ -131,6 +133,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
vi.mocked(handleAutoLock).mockResolvedValueOnce(true);
|
vi.mocked(handleAutoLock).mockResolvedValueOnce(true);
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||||
@@ -158,6 +161,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||||
onRateLimiterResponse(rateLimiterResponse);
|
onRateLimiterResponse(rateLimiterResponse);
|
||||||
@@ -168,6 +172,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
vi.mocked(handleAutoLock).mockRejectedValueOnce(new Error("No user found for this API key."));
|
vi.mocked(handleAutoLock).mockRejectedValueOnce(new Error("No user found for this API key."));
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(handleAutoLock).toHaveBeenCalledWith({
|
expect(handleAutoLock).toHaveBeenCalledWith({
|
||||||
@@ -195,6 +200,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
// Mock rate limiter to trigger the onRateLimiterResponse callback
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
(checkRateLimitAndThrowError as any).mockImplementationOnce(
|
||||||
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
({ onRateLimiterResponse }: { onRateLimiterResponse: (response: RatelimitResponse) => void }) => {
|
||||||
onRateLimiterResponse(rateLimiterResponse);
|
onRateLimiterResponse(rateLimiterResponse);
|
||||||
@@ -235,6 +241,7 @@ describe("rateLimitApiKey middleware", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// @ts-expect-error weird typing between middleware and createMocks
|
// @ts-expect-error weird typing between middleware and createMocks
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
await rateLimitApiKey(req, res, vi.fn() as any);
|
await rateLimitApiKey(req, res, vi.fn() as any);
|
||||||
|
|
||||||
expect(res._getStatusCode()).toBe(429);
|
expect(res._getStatusCode()).toBe(429);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { handleAutoLock } from "@calcom/lib/autoLock";
|
import { handleAutoLock } from "@calcom/features/ee/api-keys/lib/autoLock";
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,42 @@
|
|||||||
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
/**
|
||||||
|
* Unit Tests for verifyApiKey middleware
|
||||||
|
*
|
||||||
|
* These tests verify the middleware logic without touching the database.
|
||||||
|
* All dependencies (repositories, utilities) are mocked.
|
||||||
|
*/
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import type { NextApiRequest, NextApiResponse } from "next";
|
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 type { ILicenseKeyService } from "@calcom/ee/common/server/LicenseKeyService";
|
||||||
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
|
import LicenseKeyService, { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
|
||||||
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
|
import { PrismaApiKeyRepository } from "@calcom/lib/server/repository/PrismaApiKeyRepository";
|
||||||
import type { IDeploymentRepository } from "@calcom/lib/server/repository/deployment.interface";
|
import type { IDeploymentRepository } from "@calcom/lib/server/repository/deployment.interface";
|
||||||
import prisma from "@calcom/prisma";
|
import { ApiKeyService } from "@calcom/lib/server/service/ApiKeyService";
|
||||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
import { UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import { isAdminGuard } from "../utils/isAdmin";
|
||||||
|
import { isLockedOrBlocked } from "../utils/isLockedOrBlocked";
|
||||||
|
import { ScopeOfAdmin } from "../utils/scopeOfAdmin";
|
||||||
import { verifyApiKey } from "./verifyApiKey";
|
import { verifyApiKey } from "./verifyApiKey";
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/server/service/ApiKeyService", () => ({
|
||||||
|
ApiKeyService: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@calcom/lib/server/repository/PrismaApiKeyRepository", () => ({
|
||||||
|
PrismaApiKeyRepository: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../utils/isAdmin", () => ({
|
||||||
|
isAdminGuard: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("../utils/isLockedOrBlocked", () => ({
|
||||||
|
isLockedOrBlocked: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("@calcom/lib/crypto", () => ({
|
vi.mock("@calcom/lib/crypto", () => ({
|
||||||
symmetricDecrypt: vi.fn().mockReturnValue("mocked-decrypted-value"),
|
symmetricDecrypt: vi.fn().mockReturnValue("mocked-decrypted-value"),
|
||||||
symmetricEncrypt: vi.fn().mockReturnValue("mocked-encrypted-value"),
|
symmetricEncrypt: vi.fn().mockReturnValue("mocked-encrypted-value"),
|
||||||
@@ -32,17 +55,29 @@ afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mockDeploymentRepository: IDeploymentRepository = {
|
const mockDeploymentRepository: IDeploymentRepository = {
|
||||||
getLicenseKeyWithId: vi.fn().mockResolvedValue("mockLicenseKey"), // Mocked return value
|
getLicenseKeyWithId: vi.fn().mockResolvedValue("mockLicenseKey"),
|
||||||
getSignatureToken: vi.fn().mockResolvedValue("mockSignatureToken"),
|
getSignatureToken: vi.fn().mockResolvedValue("mockSignatureToken"),
|
||||||
};
|
};
|
||||||
|
|
||||||
describe("Verify API key", () => {
|
describe("Verify API key - Unit Tests", () => {
|
||||||
let service: ILicenseKeyService;
|
let service: ILicenseKeyService;
|
||||||
|
let mockApiKeyService: ApiKeyService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
service = await LicenseKeyService.create(mockDeploymentRepository);
|
service = await LicenseKeyService.create(mockDeploymentRepository);
|
||||||
|
|
||||||
vi.spyOn(service, "checkLicense");
|
vi.spyOn(service, "checkLicense");
|
||||||
|
|
||||||
|
vi.spyOn(LicenseKeySingleton, "getInstance").mockResolvedValue(service as LicenseKeyService);
|
||||||
|
|
||||||
|
mockApiKeyService = {
|
||||||
|
verifyKeyByHashedKey: vi.fn(),
|
||||||
|
} as unknown as ApiKeyService;
|
||||||
|
|
||||||
|
vi.mocked(ApiKeyService).mockImplementation(() => mockApiKeyService);
|
||||||
|
vi.mocked(PrismaApiKeyRepository).mockImplementation(() => ({} as unknown as PrismaApiKeyRepository));
|
||||||
|
|
||||||
|
vi.mocked(isAdminGuard).mockReset();
|
||||||
|
vi.mocked(isLockedOrBlocked).mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should throw an error if the api key is not valid", async () => {
|
it("should throw an error if the api key is not valid", async () => {
|
||||||
@@ -98,22 +133,25 @@ describe("Verify API key", () => {
|
|||||||
query: {
|
query: {
|
||||||
apiKey: "cal_test_key",
|
apiKey: "cal_test_key",
|
||||||
},
|
},
|
||||||
prisma,
|
|
||||||
});
|
});
|
||||||
const hashedKey = hashAPIKey("test_key");
|
|
||||||
await prismock.apiKey.create({
|
vi.mocked(mockApiKeyService.verifyKeyByHashedKey).mockResolvedValue({
|
||||||
data: {
|
valid: true,
|
||||||
hashedKey,
|
userId: 1,
|
||||||
user: {
|
user: {
|
||||||
create: {
|
|
||||||
email: "admin@example.com",
|
|
||||||
role: UserPermissionRole.ADMIN,
|
role: UserPermissionRole.ADMIN,
|
||||||
locked: false,
|
locked: false,
|
||||||
},
|
email: "admin@example.com",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mocked(isAdminGuard).mockResolvedValue({
|
||||||
|
isAdmin: true,
|
||||||
|
scope: ScopeOfAdmin.SystemWide,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(isLockedOrBlocked).mockResolvedValue(false);
|
||||||
|
|
||||||
const middleware = {
|
const middleware = {
|
||||||
fn: verifyApiKey,
|
fn: verifyApiKey,
|
||||||
};
|
};
|
||||||
@@ -139,40 +177,25 @@ describe("Verify API key", () => {
|
|||||||
query: {
|
query: {
|
||||||
apiKey: "cal_test_key",
|
apiKey: "cal_test_key",
|
||||||
},
|
},
|
||||||
prisma,
|
|
||||||
});
|
});
|
||||||
const hashedKey = hashAPIKey("test_key");
|
|
||||||
await prismock.apiKey.create({
|
vi.mocked(mockApiKeyService.verifyKeyByHashedKey).mockResolvedValue({
|
||||||
data: {
|
valid: true,
|
||||||
hashedKey,
|
userId: 2,
|
||||||
user: {
|
user: {
|
||||||
create: {
|
|
||||||
email: "org-admin@acme.com",
|
|
||||||
role: UserPermissionRole.USER,
|
role: UserPermissionRole.USER,
|
||||||
locked: false,
|
locked: false,
|
||||||
teams: {
|
email: "org-admin@acme.com",
|
||||||
create: {
|
|
||||||
accepted: true,
|
|
||||||
role: MembershipRole.OWNER,
|
|
||||||
team: {
|
|
||||||
create: {
|
|
||||||
name: "ACME",
|
|
||||||
isOrganization: true,
|
|
||||||
organizationSettings: {
|
|
||||||
create: {
|
|
||||||
isAdminAPIEnabled: true,
|
|
||||||
orgAutoAcceptEmail: "acme.com",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mocked(isAdminGuard).mockResolvedValue({
|
||||||
|
isAdmin: true,
|
||||||
|
scope: ScopeOfAdmin.OrgOwnerOrAdmin,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(isLockedOrBlocked).mockResolvedValue(false);
|
||||||
|
|
||||||
const middleware = {
|
const middleware = {
|
||||||
fn: verifyApiKey,
|
fn: verifyApiKey,
|
||||||
};
|
};
|
||||||
@@ -198,22 +221,25 @@ describe("Verify API key", () => {
|
|||||||
query: {
|
query: {
|
||||||
apiKey: "cal_test_key",
|
apiKey: "cal_test_key",
|
||||||
},
|
},
|
||||||
prisma,
|
|
||||||
});
|
});
|
||||||
const hashedKey = hashAPIKey("test_key");
|
|
||||||
await prismock.apiKey.create({
|
vi.mocked(mockApiKeyService.verifyKeyByHashedKey).mockResolvedValue({
|
||||||
data: {
|
valid: true,
|
||||||
hashedKey,
|
userId: 3,
|
||||||
user: {
|
user: {
|
||||||
create: {
|
|
||||||
email: "locked@example.com",
|
|
||||||
role: UserPermissionRole.USER,
|
role: UserPermissionRole.USER,
|
||||||
locked: true,
|
locked: true,
|
||||||
},
|
email: "locked@example.com",
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
vi.mocked(isAdminGuard).mockResolvedValue({
|
||||||
|
isAdmin: false,
|
||||||
|
scope: ScopeOfAdmin.SystemWide,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mocked(isLockedOrBlocked).mockResolvedValue(true);
|
||||||
|
|
||||||
const middleware = {
|
const middleware = {
|
||||||
fn: verifyApiKey,
|
fn: verifyApiKey,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,21 +3,15 @@ import type { NextMiddleware } from "next-api-middleware";
|
|||||||
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
|
import { LicenseKeySingleton } from "@calcom/ee/common/server/LicenseKeyService";
|
||||||
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
|
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
|
||||||
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
import { IS_PRODUCTION } from "@calcom/lib/constants";
|
||||||
|
import { PrismaApiKeyRepository } from "@calcom/lib/server/repository/PrismaApiKeyRepository";
|
||||||
import { DeploymentRepository } from "@calcom/lib/server/repository/deployment";
|
import { DeploymentRepository } from "@calcom/lib/server/repository/deployment";
|
||||||
import prisma from "@calcom/prisma";
|
import { ApiKeyService } from "@calcom/lib/server/service/ApiKeyService";
|
||||||
|
import { prisma } from "@calcom/prisma";
|
||||||
|
|
||||||
import { isAdminGuard } from "../utils/isAdmin";
|
import { isAdminGuard } from "../utils/isAdmin";
|
||||||
import { isLockedOrBlocked } from "../utils/isLockedOrBlocked";
|
import { isLockedOrBlocked } from "../utils/isLockedOrBlocked";
|
||||||
import { ScopeOfAdmin } from "../utils/scopeOfAdmin";
|
import { ScopeOfAdmin } from "../utils/scopeOfAdmin";
|
||||||
|
|
||||||
// Used to check if the apiKey is not expired, could be extracted if reused. but not for now.
|
|
||||||
export const dateNotInPast = function (date: Date) {
|
|
||||||
const now = new Date();
|
|
||||||
if (now.setHours(0, 0, 0, 0) > date.setHours(0, 0, 0, 0)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This verifies the apiKey and sets the user if it is valid.
|
// This verifies the apiKey and sets the user if it is valid.
|
||||||
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
||||||
const deploymentRepo = new DeploymentRepository(prisma);
|
const deploymentRepo = new DeploymentRepository(prisma);
|
||||||
@@ -32,24 +26,19 @@ export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
|||||||
|
|
||||||
const strippedApiKey = `${req.query.apiKey}`.replace(process.env.API_KEY_PREFIX || "cal_", "");
|
const strippedApiKey = `${req.query.apiKey}`.replace(process.env.API_KEY_PREFIX || "cal_", "");
|
||||||
const hashedKey = hashAPIKey(strippedApiKey);
|
const hashedKey = hashAPIKey(strippedApiKey);
|
||||||
const apiKey = await prisma.apiKey.findUnique({
|
|
||||||
where: { hashedKey },
|
// Use service layer for API key verification
|
||||||
include: {
|
const apiKeyRepo = new PrismaApiKeyRepository(prisma);
|
||||||
user: {
|
const apiKeyService = new ApiKeyService({ apiKeyRepo });
|
||||||
select: { role: true, locked: true, email: true },
|
const result = await apiKeyService.verifyKeyByHashedKey(hashedKey);
|
||||||
},
|
|
||||||
},
|
if (!result.valid) {
|
||||||
});
|
return res.status(401).json({ error: result.error });
|
||||||
if (!apiKey) return res.status(401).json({ error: "Your API key is not valid." });
|
|
||||||
if (apiKey.expiresAt && dateNotInPast(apiKey.expiresAt)) {
|
|
||||||
return res.status(401).json({ error: "This API key is expired." });
|
|
||||||
}
|
}
|
||||||
if (!apiKey.userId || !apiKey.user)
|
|
||||||
return res.status(404).json({ error: "No user found for this API key." });
|
|
||||||
|
|
||||||
// save the user id in the request for later use
|
// save the user id in the request for later use
|
||||||
req.userId = apiKey.userId;
|
req.userId = result.userId!;
|
||||||
req.user = apiKey.user;
|
req.user = result.user!;
|
||||||
|
|
||||||
const { isAdmin, scope } = await isAdminGuard(req);
|
const { isAdmin, scope } = await isAdminGuard(req);
|
||||||
const userIsLockedOrBlocked = await isLockedOrBlocked(req);
|
const userIsLockedOrBlocked = await isLockedOrBlocked(req);
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
|
|
||||||
|
export const eventTypeSelect = {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
slug: true,
|
||||||
|
length: true,
|
||||||
|
hidden: true,
|
||||||
|
position: true,
|
||||||
|
userId: true,
|
||||||
|
teamId: true,
|
||||||
|
scheduleId: true,
|
||||||
|
eventName: true,
|
||||||
|
timeZone: true,
|
||||||
|
periodType: true,
|
||||||
|
periodStartDate: true,
|
||||||
|
periodEndDate: true,
|
||||||
|
periodDays: true,
|
||||||
|
periodCountCalendarDays: true,
|
||||||
|
requiresConfirmation: true,
|
||||||
|
recurringEvent: true,
|
||||||
|
disableGuests: true,
|
||||||
|
hideCalendarNotes: true,
|
||||||
|
minimumBookingNotice: true,
|
||||||
|
beforeEventBuffer: true,
|
||||||
|
afterEventBuffer: true,
|
||||||
|
schedulingType: true,
|
||||||
|
price: true,
|
||||||
|
currency: true,
|
||||||
|
slotInterval: true,
|
||||||
|
parentId: true,
|
||||||
|
successRedirectUrl: true,
|
||||||
|
description: true,
|
||||||
|
locations: true,
|
||||||
|
metadata: true,
|
||||||
|
seatsPerTimeSlot: true,
|
||||||
|
seatsShowAttendees: true,
|
||||||
|
seatsShowAvailabilityCount: true,
|
||||||
|
bookingFields: true,
|
||||||
|
bookingLimits: true,
|
||||||
|
onlyShowFirstAvailableSlot: true,
|
||||||
|
durationLimits: true,
|
||||||
|
customInputs: {
|
||||||
|
select: { id: true, label: true, required: true, options: true, type: true, placeholder: true },
|
||||||
|
},
|
||||||
|
hashedLink: {
|
||||||
|
select: { link: true },
|
||||||
|
},
|
||||||
|
team: {
|
||||||
|
select: { slug: true },
|
||||||
|
},
|
||||||
|
hosts: {
|
||||||
|
select: {
|
||||||
|
userId: true,
|
||||||
|
isFixed: true,
|
||||||
|
scheduleId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
owner: {
|
||||||
|
select: { username: true, id: true },
|
||||||
|
},
|
||||||
|
children: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
userId: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Prisma.EventTypeSelect;
|
||||||
@@ -1,9 +1,17 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
|
import { sentrySpan } from "@calcom/features/watchlist/lib/telemetry";
|
||||||
import { checkIfEmailIsBlockedInWatchlistController } 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 checkIfEmailIsBlockedInWatchlistController(user.email));
|
return (
|
||||||
|
user.locked ||
|
||||||
|
(await checkIfEmailIsBlockedInWatchlistController({
|
||||||
|
email: user.email,
|
||||||
|
organizationId: null,
|
||||||
|
span: sentrySpan,
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _ApiKeyModel as ApiKey } from "@calcom/prisma/zod";
|
import { ApiKeySchema as ApiKey } from "@calcom/prisma/zod/modelSchema/ApiKeySchema";
|
||||||
|
|
||||||
export const apiKeyCreateBodySchema = ApiKey.pick({
|
export const apiKeyCreateBodySchema = ApiKey.pick({
|
||||||
note: true,
|
note: true,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { emailSchema } from "@calcom/lib/emailSchema";
|
import { emailSchema } from "@calcom/lib/emailSchema";
|
||||||
import { _AttendeeModel as Attendee } from "@calcom/prisma/zod";
|
import { AttendeeSchema } from "@calcom/prisma/zod/modelSchema/AttendeeSchema";
|
||||||
|
|
||||||
import { timeZone } from "~/lib/validations/shared/timeZone";
|
import { timeZone } from "~/lib/validations/shared/timeZone";
|
||||||
|
|
||||||
export const schemaAttendeeBaseBodyParams = Attendee.pick({
|
export const schemaAttendeeBaseBodyParams = AttendeeSchema.pick({
|
||||||
bookingId: true,
|
bookingId: true,
|
||||||
email: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
@@ -31,7 +31,7 @@ const schemaAttendeeEditParams = z
|
|||||||
export const schemaAttendeeEditBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeEditParams);
|
export const schemaAttendeeEditBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeEditParams);
|
||||||
export const schemaAttendeeCreateBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeCreateParams);
|
export const schemaAttendeeCreateBodyParams = schemaAttendeeBaseBodyParams.merge(schemaAttendeeCreateParams);
|
||||||
|
|
||||||
export const schemaAttendeeReadPublic = Attendee.pick({
|
export const schemaAttendeeReadPublic = AttendeeSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
bookingId: true,
|
bookingId: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _AvailabilityModel as Availability, _ScheduleModel as Schedule } from "@calcom/prisma/zod";
|
|
||||||
import { denullishShape } from "@calcom/prisma/zod-utils";
|
import { denullishShape } from "@calcom/prisma/zod-utils";
|
||||||
|
import { AvailabilitySchema } from "@calcom/prisma/zod/modelSchema/AvailabilitySchema";
|
||||||
|
import { ScheduleSchema } from "@calcom/prisma/zod/modelSchema/ScheduleSchema";
|
||||||
|
|
||||||
export const schemaAvailabilityBaseBodyParams = /** We make all these properties required */ denullishShape(
|
export const schemaAvailabilityBaseBodyParams = /** We make all these properties required */ denullishShape(
|
||||||
Availability.pick({
|
AvailabilitySchema.pick({
|
||||||
/** We need to pass the schedule where this availability belongs to */
|
/** We need to pass the schedule where this availability belongs to */
|
||||||
scheduleId: true,
|
scheduleId: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const schemaAvailabilityReadPublic = Availability.pick({
|
export const schemaAvailabilityReadPublic = AvailabilitySchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
startTime: true,
|
startTime: true,
|
||||||
endTime: true,
|
endTime: true,
|
||||||
@@ -19,7 +20,7 @@ export const schemaAvailabilityReadPublic = Availability.pick({
|
|||||||
days: true,
|
days: true,
|
||||||
// eventTypeId: true /** @deprecated */,
|
// eventTypeId: true /** @deprecated */,
|
||||||
// userId: true /** @deprecated */,
|
// userId: true /** @deprecated */,
|
||||||
}).merge(z.object({ success: z.boolean().optional(), Schedule: Schedule.partial() }).partial());
|
}).merge(z.object({ success: z.boolean().optional(), Schedule: ScheduleSchema.partial() }).partial());
|
||||||
|
|
||||||
const schemaAvailabilityCreateParams = z
|
const schemaAvailabilityCreateParams = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { _BookingReferenceModel as BookingReference } from "@calcom/prisma/zod";
|
|
||||||
import { denullishShape } from "@calcom/prisma/zod-utils";
|
import { denullishShape } from "@calcom/prisma/zod-utils";
|
||||||
|
import { BookingReferenceSchema } from "@calcom/prisma/zod/modelSchema/BookingReferenceSchema";
|
||||||
|
|
||||||
export const schemaBookingReferenceBaseBodyParams = BookingReference.pick({
|
export const schemaBookingReferenceBaseBodyParams = BookingReferenceSchema.pick({
|
||||||
type: true,
|
type: true,
|
||||||
bookingId: true,
|
bookingId: true,
|
||||||
uid: true,
|
uid: true,
|
||||||
@@ -11,7 +11,7 @@ export const schemaBookingReferenceBaseBodyParams = BookingReference.pick({
|
|||||||
deleted: true,
|
deleted: true,
|
||||||
}).partial();
|
}).partial();
|
||||||
|
|
||||||
export const schemaBookingReferenceReadPublic = BookingReference.pick({
|
export const schemaBookingReferenceReadPublic = BookingReferenceSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
type: true,
|
type: true,
|
||||||
bookingId: true,
|
bookingId: true,
|
||||||
@@ -22,7 +22,7 @@ export const schemaBookingReferenceReadPublic = BookingReference.pick({
|
|||||||
deleted: true,
|
deleted: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemaBookingCreateBodyParams = BookingReference.omit({ id: true, bookingId: true })
|
export const schemaBookingCreateBodyParams = BookingReferenceSchema.omit({ id: true, bookingId: true })
|
||||||
.merge(denullishShape(BookingReference.pick({ bookingId: true })))
|
.merge(denullishShape(BookingReferenceSchema.pick({ bookingId: true })))
|
||||||
.strict();
|
.strict();
|
||||||
export const schemaBookingEditBodyParams = schemaBookingCreateBodyParams.partial();
|
export const schemaBookingEditBodyParams = schemaBookingCreateBodyParams.partial();
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { extendedBookingCreateBody } from "@calcom/features/bookings/lib/bookingCreateBodySchema";
|
||||||
_AttendeeModel,
|
|
||||||
_BookingModel as Booking,
|
|
||||||
_EventTypeModel,
|
|
||||||
_PaymentModel,
|
|
||||||
_TeamModel,
|
|
||||||
_UserModel,
|
|
||||||
} from "@calcom/prisma/zod";
|
|
||||||
import { iso8601 } from "@calcom/prisma/zod-utils";
|
import { iso8601 } from "@calcom/prisma/zod-utils";
|
||||||
import { extendedBookingCreateBody } from "@calcom/prisma/zod/custom/booking";
|
import { AttendeeSchema } from "@calcom/prisma/zod/modelSchema/AttendeeSchema";
|
||||||
|
import { BookingSchema as Booking } from "@calcom/prisma/zod/modelSchema/BookingSchema";
|
||||||
|
import { EventTypeSchema } from "@calcom/prisma/zod/modelSchema/EventTypeSchema";
|
||||||
|
import { PaymentSchema } from "@calcom/prisma/zod/modelSchema/PaymentSchema";
|
||||||
|
import { TeamSchema } from "@calcom/prisma/zod/modelSchema/TeamSchema";
|
||||||
|
import { UserSchema } from "@calcom/prisma/zod/modelSchema/UserSchema";
|
||||||
|
|
||||||
import { schemaQueryUserId } from "./shared/queryUserId";
|
import { schemaQueryUserId } from "./shared/queryUserId";
|
||||||
|
|
||||||
@@ -72,14 +70,13 @@ export const schemaBookingEditBodyParams = schemaBookingBaseBodyParams
|
|||||||
.merge(schemaBookingEditParams)
|
.merge(schemaBookingEditParams)
|
||||||
.omit({ uid: true });
|
.omit({ uid: true });
|
||||||
|
|
||||||
const teamSchema = _TeamModel.pick({
|
const teamSchema = TeamSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemaBookingReadPublic = Booking.extend({
|
export const schemaBookingReadPublic = Booking.extend({
|
||||||
eventType: _EventTypeModel
|
eventType: EventTypeSchema.pick({
|
||||||
.pick({
|
|
||||||
title: true,
|
title: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
})
|
})
|
||||||
@@ -91,7 +88,7 @@ export const schemaBookingReadPublic = Booking.extend({
|
|||||||
.nullish(),
|
.nullish(),
|
||||||
attendees: z
|
attendees: z
|
||||||
.array(
|
.array(
|
||||||
_AttendeeModel.pick({
|
AttendeeSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
@@ -100,17 +97,15 @@ export const schemaBookingReadPublic = Booking.extend({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
user: _UserModel
|
user: UserSchema.pick({
|
||||||
.pick({
|
|
||||||
email: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
locale: true,
|
locale: true,
|
||||||
})
|
}).nullish(),
|
||||||
.nullish(),
|
|
||||||
payment: z
|
payment: z
|
||||||
.array(
|
.array(
|
||||||
_PaymentModel.pick({
|
PaymentSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
success: true,
|
success: true,
|
||||||
paymentOption: true,
|
paymentOption: true,
|
||||||
@@ -140,3 +135,8 @@ export const schemaBookingReadPublic = Booking.extend({
|
|||||||
rescheduledBy: true,
|
rescheduledBy: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
bookingCreateSchemaLegacyPropsForApi,
|
||||||
|
bookingCreateBodySchemaForApi,
|
||||||
|
} from "@calcom/features/bookings/lib/bookingCreateBodySchema";
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _DestinationCalendarModel as DestinationCalendar } from "@calcom/prisma/zod";
|
import { DestinationCalendarSchema } from "@calcom/prisma/zod/modelSchema/DestinationCalendarSchema";
|
||||||
|
|
||||||
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendar.pick({
|
export const schemaDestinationCalendarBaseBodyParams = DestinationCalendarSchema.pick({
|
||||||
integration: true,
|
integration: true,
|
||||||
externalId: true,
|
externalId: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
@@ -38,7 +38,7 @@ export const schemaDestinationCalendarEditBodyParams = schemaDestinationCalendar
|
|||||||
schemaDestinationCalendarEditParams
|
schemaDestinationCalendarEditParams
|
||||||
);
|
);
|
||||||
|
|
||||||
export const schemaDestinationCalendarReadPublic = DestinationCalendar.pick({
|
export const schemaDestinationCalendarReadPublic = DestinationCalendarSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
integration: true,
|
integration: true,
|
||||||
externalId: true,
|
externalId: true,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { _EventTypeCustomInputModel as EventTypeCustomInput } from "@calcom/prisma/zod";
|
import { EventTypeCustomInputSchema } from "@calcom/prisma/zod/modelSchema/EventTypeCustomInputSchema";
|
||||||
|
|
||||||
export const schemaEventTypeCustomInputBaseBodyParams = EventTypeCustomInput.omit({
|
export const schemaEventTypeCustomInputBaseBodyParams = EventTypeCustomInputSchema.omit({
|
||||||
id: true,
|
id: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemaEventTypeCustomInputPublic = EventTypeCustomInput.omit({});
|
export const schemaEventTypeCustomInputPublic = EventTypeCustomInputSchema.omit({});
|
||||||
|
|
||||||
export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.strict();
|
export const schemaEventTypeCustomInputBodyParams = schemaEventTypeCustomInputBaseBodyParams.strict();
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import {
|
|||||||
MIN_EVENT_DURATION_MINUTES,
|
MIN_EVENT_DURATION_MINUTES,
|
||||||
} from "@calcom/lib/constants";
|
} from "@calcom/lib/constants";
|
||||||
import slugify from "@calcom/lib/slugify";
|
import slugify from "@calcom/lib/slugify";
|
||||||
import { _EventTypeModel as EventType, _HostModel } from "@calcom/prisma/zod";
|
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
||||||
import { customInputSchema, eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
import { EventTypeSchema } from "@calcom/prisma/zod/modelSchema/EventTypeSchema";
|
||||||
|
import { HostSchema } from "@calcom/prisma/zod/modelSchema/HostSchema";
|
||||||
|
|
||||||
import { Frequency } from "~/lib/types";
|
import { Frequency } from "~/lib/types";
|
||||||
|
|
||||||
import { jsonSchema } from "./shared/jsonSchema";
|
|
||||||
import { schemaQueryUserId } from "./shared/queryUserId";
|
import { schemaQueryUserId } from "./shared/queryUserId";
|
||||||
import { timeZone } from "./shared/timeZone";
|
import { timeZone } from "./shared/timeZone";
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ const recurringEventInputSchema = z.object({
|
|||||||
tzid: timeZone.optional(),
|
tzid: timeZone.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const hostSchema = _HostModel.pick({
|
const hostSchema = HostSchema.pick({
|
||||||
isFixed: true,
|
isFixed: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
scheduleId: true,
|
scheduleId: true,
|
||||||
@@ -35,7 +35,7 @@ export const childrenSchema = z.object({
|
|||||||
userId: z.number().int(),
|
userId: z.number().int(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemaEventTypeBaseBodyParams = EventType.pick({
|
export const schemaEventTypeBaseBodyParams = EventTypeSchema.pick({
|
||||||
title: true,
|
title: true,
|
||||||
description: true,
|
description: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
@@ -117,67 +117,3 @@ const schemaEventTypeEditParams = z
|
|||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export const schemaEventTypeEditBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeEditParams);
|
export const schemaEventTypeEditBodyParams = schemaEventTypeBaseBodyParams.merge(schemaEventTypeEditParams);
|
||||||
export const schemaEventTypeReadPublic = EventType.pick({
|
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
slug: true,
|
|
||||||
length: true,
|
|
||||||
hidden: true,
|
|
||||||
position: true,
|
|
||||||
userId: true,
|
|
||||||
teamId: true,
|
|
||||||
scheduleId: true,
|
|
||||||
eventName: true,
|
|
||||||
timeZone: true,
|
|
||||||
periodType: true,
|
|
||||||
periodStartDate: true,
|
|
||||||
periodEndDate: true,
|
|
||||||
periodDays: true,
|
|
||||||
periodCountCalendarDays: true,
|
|
||||||
requiresConfirmation: true,
|
|
||||||
recurringEvent: true,
|
|
||||||
disableGuests: true,
|
|
||||||
hideCalendarNotes: true,
|
|
||||||
minimumBookingNotice: true,
|
|
||||||
beforeEventBuffer: true,
|
|
||||||
afterEventBuffer: true,
|
|
||||||
schedulingType: true,
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
slotInterval: true,
|
|
||||||
parentId: true,
|
|
||||||
successRedirectUrl: true,
|
|
||||||
description: true,
|
|
||||||
locations: true,
|
|
||||||
metadata: true,
|
|
||||||
seatsPerTimeSlot: true,
|
|
||||||
seatsShowAttendees: true,
|
|
||||||
seatsShowAvailabilityCount: true,
|
|
||||||
bookingFields: true,
|
|
||||||
bookingLimits: true,
|
|
||||||
onlyShowFirstAvailableSlot: true,
|
|
||||||
durationLimits: true,
|
|
||||||
}).merge(
|
|
||||||
z.object({
|
|
||||||
children: z.array(childrenSchema).optional().default([]),
|
|
||||||
hosts: z.array(hostSchema).optional().default([]),
|
|
||||||
locations: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
link: z.string().optional(),
|
|
||||||
address: z.string().optional(),
|
|
||||||
hostPhoneNumber: z.string().optional(),
|
|
||||||
type: z.any().optional(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.nullable(),
|
|
||||||
metadata: jsonSchema.nullable(),
|
|
||||||
customInputs: customInputSchema.array().optional(),
|
|
||||||
link: z.string().optional(),
|
|
||||||
hashedLink: z
|
|
||||||
.array(z.object({ link: z.string() }))
|
|
||||||
.optional()
|
|
||||||
.default([]),
|
|
||||||
bookingFields: eventTypeBookingFields.optional().nullable(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
@@ -1,19 +1,24 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
import { _MembershipModel as Membership, _TeamModel } from "@calcom/prisma/zod";
|
|
||||||
import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
import { stringOrNumber } from "@calcom/prisma/zod-utils";
|
||||||
|
import { MembershipSchema } from "@calcom/prisma/zod/modelSchema/MembershipSchema";
|
||||||
|
import { TeamSchema } from "@calcom/prisma/zod/modelSchema/TeamSchema";
|
||||||
|
|
||||||
import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString";
|
import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString";
|
||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
export const schemaMembershipBaseBodyParams = Membership.omit({});
|
export const schemaMembershipBaseBodyParams = MembershipSchema.omit({});
|
||||||
|
|
||||||
const schemaMembershipRequiredParams = z.object({
|
const schemaMembershipRequiredParams = z.object({
|
||||||
teamId: z.number(),
|
teamId: z.number(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const membershipCreateBodySchema = Membership.omit({ id: true })
|
export const membershipCreateBodySchema = MembershipSchema.omit({
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
|
})
|
||||||
.partial({
|
.partial({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
role: true,
|
role: true,
|
||||||
@@ -26,11 +31,13 @@ export const membershipCreateBodySchema = Membership.omit({ id: true })
|
|||||||
...v,
|
...v,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const membershipEditBodySchema = Membership.omit({
|
export const membershipEditBodySchema = MembershipSchema.omit({
|
||||||
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
||||||
teamId: true,
|
teamId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
id: true,
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
updatedAt: true,
|
||||||
})
|
})
|
||||||
.partial({
|
.partial({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
@@ -43,7 +50,7 @@ export const schemaMembershipBodyParams = schemaMembershipBaseBodyParams.merge(
|
|||||||
schemaMembershipRequiredParams
|
schemaMembershipRequiredParams
|
||||||
);
|
);
|
||||||
|
|
||||||
export const schemaMembershipPublic = Membership.merge(z.object({ team: _TeamModel }).partial());
|
export const schemaMembershipPublic = MembershipSchema.merge(z.object({ team: TeamSchema }).partial());
|
||||||
|
|
||||||
/** We extract userId and teamId from compound ID string */
|
/** We extract userId and teamId from compound ID string */
|
||||||
export const membershipIdSchema = schemaQueryIdAsString
|
export const membershipIdSchema = schemaQueryIdAsString
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { _PaymentModel as Payment } from "@calcom/prisma/zod";
|
import { PaymentSchema } from "@calcom/prisma/zod/modelSchema/PaymentSchema";
|
||||||
|
|
||||||
export const schemaPaymentPublic = Payment.pick({
|
export const schemaPaymentPublic = PaymentSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
amount: true,
|
amount: true,
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _ReminderMailModel as ReminderMail } from "@calcom/prisma/zod";
|
import { ReminderMailSchema } from "@calcom/prisma/zod/modelSchema/ReminderMailSchema";
|
||||||
|
|
||||||
export const schemaReminderMailBaseBodyParams = ReminderMail.omit({ id: true }).partial();
|
export const schemaReminderMailBaseBodyParams = ReminderMailSchema.omit({ id: true }).partial();
|
||||||
|
|
||||||
export const schemaReminderMailPublic = ReminderMail.omit({});
|
export const schemaReminderMailPublic = ReminderMailSchema.omit({});
|
||||||
|
|
||||||
const schemaReminderMailRequiredParams = z.object({
|
const schemaReminderMailRequiredParams = z.object({
|
||||||
referenceId: z.number().int(),
|
referenceId: z.number().int(),
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
import { _ScheduleModel as Schedule, _AvailabilityModel as Availability } from "@calcom/prisma/zod";
|
import { AvailabilitySchema } from "@calcom/prisma/zod/modelSchema/AvailabilitySchema";
|
||||||
|
import { ScheduleSchema } from "@calcom/prisma/zod/modelSchema/ScheduleSchema";
|
||||||
|
|
||||||
import { timeZone } from "./shared/timeZone";
|
import { timeZone } from "./shared/timeZone";
|
||||||
|
|
||||||
const schemaScheduleBaseBodyParams = Schedule.omit({ id: true, timeZone: true }).partial();
|
const schemaScheduleBaseBodyParams = ScheduleSchema.omit({ id: true, timeZone: true }).partial();
|
||||||
|
|
||||||
export const schemaSingleScheduleBodyParams = schemaScheduleBaseBodyParams.merge(
|
export const schemaSingleScheduleBodyParams = schemaScheduleBaseBodyParams.merge(
|
||||||
z.object({ userId: z.number().optional(), timeZone: timeZone.optional() })
|
z.object({ userId: z.number().optional(), timeZone: timeZone.optional() })
|
||||||
@@ -17,12 +18,12 @@ export const schemaCreateScheduleBodyParams = schemaScheduleBaseBodyParams.merge
|
|||||||
|
|
||||||
export const schemaSchedulePublic = z
|
export const schemaSchedulePublic = z
|
||||||
.object({ id: z.number() })
|
.object({ id: z.number() })
|
||||||
.merge(Schedule)
|
.merge(ScheduleSchema)
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
availability: z
|
availability: z
|
||||||
.array(
|
.array(
|
||||||
Availability.pick({
|
AvailabilitySchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
date: true,
|
date: true,
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
import { _SelectedCalendarModel as SelectedCalendar } from "@calcom/prisma/zod";
|
import { SelectedCalendarSchema } from "@calcom/prisma/zod/modelSchema/SelectedCalendarSchema";
|
||||||
|
|
||||||
import { schemaQueryIdAsString } from "./shared/queryIdString";
|
import { schemaQueryIdAsString } from "./shared/queryIdString";
|
||||||
import { schemaQueryIdParseInt } from "./shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "./shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
export const schemaSelectedCalendarBaseBodyParams = SelectedCalendar;
|
export const schemaSelectedCalendarBaseBodyParams = SelectedCalendarSchema;
|
||||||
|
|
||||||
export const schemaSelectedCalendarPublic = SelectedCalendar.omit({});
|
export const schemaSelectedCalendarPublic = SelectedCalendarSchema.omit({});
|
||||||
|
|
||||||
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams
|
export const schemaSelectedCalendarBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||||
.partial({
|
.pick({
|
||||||
|
integration: true,
|
||||||
|
externalId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
})
|
})
|
||||||
.omit({
|
.partial({
|
||||||
// id will be set by the database
|
userId: true,
|
||||||
id: true,
|
|
||||||
// No eventTypeId support in API v1
|
|
||||||
eventTypeId: true,
|
|
||||||
|
|
||||||
/** No watch related fields support in API v1 */
|
|
||||||
watchAttempts: true,
|
|
||||||
unwatchAttempts: true,
|
|
||||||
maxAttempts: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams
|
export const schemaSelectedCalendarUpdateBodyParams = schemaSelectedCalendarBaseBodyParams
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { _TeamModel as Team } from "@calcom/prisma/zod";
|
import { TeamSchema } from "@calcom/prisma/zod/modelSchema/TeamSchema";
|
||||||
|
|
||||||
export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({
|
export const schemaTeamBaseBodyParams = TeamSchema.omit({ id: true, createdAt: true }).partial({
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
pendingPayment: true,
|
pendingPayment: true,
|
||||||
@@ -28,6 +28,6 @@ const schemaOwnerId = z.object({
|
|||||||
|
|
||||||
export const schemaTeamCreateBodyParams = schemaTeamBodyParams.merge(schemaOwnerId).strict();
|
export const schemaTeamCreateBodyParams = schemaTeamBodyParams.merge(schemaOwnerId).strict();
|
||||||
|
|
||||||
export const schemaTeamReadPublic = Team.omit({});
|
export const schemaTeamReadPublic = TeamSchema.omit({});
|
||||||
|
|
||||||
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { checkUsername } from "@calcom/features/profile/lib/checkUsername";
|
||||||
import { emailSchema } from "@calcom/lib/emailSchema";
|
import { emailSchema } from "@calcom/lib/emailSchema";
|
||||||
import { checkUsername } from "@calcom/lib/server/checkUsername";
|
|
||||||
import { _UserModel as User } from "@calcom/prisma/zod";
|
|
||||||
import { iso8601 } from "@calcom/prisma/zod-utils";
|
import { iso8601 } from "@calcom/prisma/zod-utils";
|
||||||
|
import { UserSchema } from "@calcom/prisma/zod/modelSchema/UserSchema";
|
||||||
|
|
||||||
import { isValidBase64Image } from "~/lib/utils/isValidBase64Image";
|
import { isValidBase64Image } from "~/lib/utils/isValidBase64Image";
|
||||||
import { timeZone } from "~/lib/validations/shared/timeZone";
|
import { timeZone } from "~/lib/validations/shared/timeZone";
|
||||||
@@ -68,7 +68,7 @@ const usernameSchema = z
|
|||||||
});
|
});
|
||||||
|
|
||||||
// @note: These are the values that are editable via PATCH method on the user Model
|
// @note: These are the values that are editable via PATCH method on the user Model
|
||||||
export const schemaUserBaseBodyParams = User.pick({
|
export const schemaUserBaseBodyParams = UserSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
email: true,
|
email: true,
|
||||||
username: true,
|
username: true,
|
||||||
@@ -151,7 +151,7 @@ export const schemaUserCreateBodyParams = schemaUserBaseBodyParams
|
|||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
// @note: These are the values that are always returned when reading a user
|
// @note: These are the values that are always returned when reading a user
|
||||||
export const schemaUserReadPublic = User.pick({
|
export const schemaUserReadPublic = UserSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { WEBHOOK_TRIGGER_EVENTS } from "@calcom/features/webhooks/lib/constants";
|
import { WEBHOOK_TRIGGER_EVENTS } from "@calcom/features/webhooks/lib/constants";
|
||||||
import { _WebhookModel as Webhook } from "@calcom/prisma/zod";
|
import { WebhookSchema } from "@calcom/prisma/zod/modelSchema/WebhookSchema";
|
||||||
|
|
||||||
const schemaWebhookBaseBodyParams = Webhook.pick({
|
const schemaWebhookBaseBodyParams = WebhookSchema.pick({
|
||||||
userId: true,
|
userId: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
eventTriggers: true,
|
eventTriggers: true,
|
||||||
@@ -38,7 +38,7 @@ export const schemaWebhookEditBodyParams = schemaWebhookBaseBodyParams
|
|||||||
.partial()
|
.partial()
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export const schemaWebhookReadPublic = Webhook.pick({
|
export const schemaWebhookReadPublic = WebhookSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
eventTypeId: true,
|
eventTypeId: true,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const { withAxiom } = require("next-axiom");
|
const { withAxiom } = require("next-axiom");
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
const { withSentryConfig } = require("@sentry/nextjs");
|
||||||
|
const { PrismaPlugin } = require("@prisma/nextjs-monorepo-workaround-plugin");
|
||||||
|
|
||||||
const plugins = [withAxiom];
|
const plugins = [withAxiom];
|
||||||
|
|
||||||
@@ -14,6 +15,12 @@ const nextConfig = {
|
|||||||
"@calcom/prisma",
|
"@calcom/prisma",
|
||||||
"@calcom/trpc",
|
"@calcom/trpc",
|
||||||
],
|
],
|
||||||
|
webpack: (config, { isServer }) => {
|
||||||
|
if (isServer) {
|
||||||
|
config.plugins = [...config.plugins, new PrismaPlugin()];
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
+13
-11
@@ -10,7 +10,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf .next",
|
||||||
"dev": "PORT=3003 next dev",
|
"dev": "PORT=3003 next dev",
|
||||||
"lint": "eslint . --ignore-path .gitignore",
|
"lint": "eslint .",
|
||||||
"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",
|
||||||
@@ -18,22 +18,24 @@
|
|||||||
"type-check:ci": "tsc-absolute --pretty --noEmit"
|
"type-check:ci": "tsc-absolute --pretty --noEmit"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@calcom/tsconfig": "*",
|
"@calcom/eslint-config": "workspace:*",
|
||||||
"@calcom/types": "*",
|
"@calcom/tsconfig": "workspace:*",
|
||||||
|
"@calcom/types": "workspace:*",
|
||||||
"node-mocks-http": "^1.11.0"
|
"node-mocks-http": "^1.11.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@calcom/app-store": "*",
|
"@calcom/app-store": "workspace:*",
|
||||||
"@calcom/dayjs": "*",
|
"@calcom/dayjs": "workspace:*",
|
||||||
"@calcom/emails": "*",
|
"@calcom/emails": "workspace:*",
|
||||||
"@calcom/features": "*",
|
"@calcom/features": "workspace:*",
|
||||||
"@calcom/lib": "*",
|
"@calcom/lib": "workspace:*",
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "workspace:*",
|
||||||
"@calcom/trpc": "*",
|
"@calcom/trpc": "workspace:*",
|
||||||
|
"@prisma/nextjs-monorepo-workaround-plugin": "^6.16.1",
|
||||||
"@sentry/nextjs": "^9.15.0",
|
"@sentry/nextjs": "^9.15.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "^15.3.0",
|
"next": "^15.4.5",
|
||||||
"next-api-middleware": "^1.0.1",
|
"next-api-middleware": "^1.0.1",
|
||||||
"next-axiom": "^0.17.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-swagger-doc": "^0.3.6",
|
"next-swagger-doc": "^0.3.6",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import type { Ensure } from "@calcom/types/utils";
|
import type { Ensure } from "@calcom/types/utils";
|
||||||
|
|
||||||
import { apiKeyPublicSchema } from "~/lib/validations/api-key";
|
import { apiKeyPublicSchema } from "~/lib/validations/api-key";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import type { Prisma } from "@prisma/client";
|
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
|
|
||||||
@@ -6,6 +5,7 @@ import { generateUniqueAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
|
|
||||||
import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key";
|
import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key";
|
||||||
|
|
||||||
|
|||||||
@@ -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/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
|
|
||||||
import { schemaAttendeeReadPublic } from "~/lib/validations/attendee";
|
import { schemaAttendeeReadPublic } from "~/lib/validations/attendee";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { getUserAvailabilityService } from "@calcom/lib/di/containers/get-user-availability";
|
import { getUserAvailabilityService } from "@calcom/features/di/containers/GetUserAvailability";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
@@ -191,7 +191,7 @@ const availabilitySchema = z
|
|||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { isSystemWideAdmin, userId: reqUserId } = req;
|
const { isSystemWideAdmin, userId: reqUserId } = req;
|
||||||
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
|
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
|
||||||
const userAvailabilityService = getUserAvailabilityService()
|
const userAvailabilityService = getUserAvailabilityService();
|
||||||
if (!teamId)
|
if (!teamId)
|
||||||
return userAvailabilityService.getUserAvailability({
|
return userAvailabilityService.getUserAvailability({
|
||||||
username,
|
username,
|
||||||
|
|||||||
Loaded 100 of 3038 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user