Compare commits
806
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31da30b982 | ||
|
|
d149f8385e | ||
|
|
ea5a0bd5f0 | ||
|
|
67c9fd92e1 | ||
|
|
50be997753 | ||
|
|
f00c14d0c1 | ||
|
|
8d4dd0241a | ||
|
|
90f97c3c92 | ||
|
|
1407886612 | ||
|
|
139e47fe3b | ||
|
|
9d4ecf2baa | ||
|
|
9d1cf3e70e | ||
|
|
6beb279a27 | ||
|
|
69e4b12f52 | ||
|
|
b8098ed3ab | ||
|
|
db55b11034 | ||
|
|
2273b7ea24 | ||
|
|
0a90212980 | ||
|
|
2efcfab715 | ||
|
|
3c1dfe8b74 | ||
|
|
e47ddf9835 | ||
|
|
08c62b554e | ||
|
|
b0b94b91fe | ||
|
|
af61b6d341 | ||
|
|
28693aba96 | ||
|
|
2dc0e2a6f2 | ||
|
|
6cbe1c1cf1 | ||
|
|
1c2da58d2a | ||
|
|
7bfdaa3d64 | ||
|
|
c38d2afcf5 | ||
|
|
807848cce2 | ||
|
|
e9d409c0d5 | ||
|
|
0c43d60039 | ||
|
|
1173822344 | ||
|
|
a934cec51a | ||
|
|
257acc3f71 | ||
|
|
91063c41f8 | ||
|
|
1e1298fdbc | ||
|
|
95120e1ff4 | ||
|
|
ee00f0d52c | ||
|
|
246abe0dd9 | ||
|
|
14e845ea1f | ||
|
|
8ba65c4450 | ||
|
|
aff494f21c | ||
|
|
18ad7e348e | ||
|
|
78f3d76ae2 | ||
|
|
6f9e04c6d9 | ||
|
|
fdb0e5d004 | ||
|
|
268a142370 | ||
|
|
29a3892669 | ||
|
|
0223027958 | ||
|
|
d7df154059 | ||
|
|
51d78e6f9d | ||
|
|
d5894d9fbc | ||
|
|
8b0539028e | ||
|
|
c7600d8ead | ||
|
|
6f5b8ead75 | ||
|
|
fc159fb9ea | ||
|
|
b3297f2843 | ||
|
|
1dade81299 | ||
|
|
b2839830c8 | ||
|
|
fa884e8516 | ||
|
|
3418a25a85 | ||
|
|
89cc175d73 | ||
|
|
c7c8564b63 | ||
|
|
b620581d7a | ||
|
|
c0530cc91c | ||
|
|
f738b3bc0a | ||
|
|
0853b4ca35 | ||
|
|
8be05b1dc5 | ||
|
|
71515ad17c | ||
|
|
66234ffba9 | ||
|
|
66c5f810ea | ||
|
|
81d9d84b4a | ||
|
|
6ddc666827 | ||
|
|
01346abb85 | ||
|
|
3d9650c841 | ||
|
|
f8ede75dd8 | ||
|
|
435587f043 | ||
|
|
e5abe93940 | ||
|
|
c3a5158a53 | ||
|
|
a9e14b9816 | ||
|
|
e207f40611 | ||
|
|
4ca2076c72 | ||
|
|
959b803d84 | ||
|
|
7f6136004c | ||
|
|
bb79368b2d | ||
|
|
70825dd138 | ||
|
|
05c399db9c | ||
|
|
57776b829b | ||
|
|
7fd28c39f3 | ||
|
|
ff17aa79fc | ||
|
|
578be32936 | ||
|
|
b183239ff5 | ||
|
|
013e614397 | ||
|
|
6843ced3db | ||
|
|
7318015a6e | ||
|
|
fc4d0a6aa3 | ||
|
|
8e0cdf3671 | ||
|
|
fcd9480f9f | ||
|
|
09ddbe886f | ||
|
|
4ac8a51bc2 | ||
|
|
82951cd04e | ||
|
|
ae87fdcc9f | ||
|
|
35e29c4bee | ||
|
|
387a1704f3 | ||
|
|
3fb8c5bbc5 | ||
|
|
626585f5d0 | ||
|
|
4d3727db12 | ||
|
|
f6641d5173 | ||
|
|
f9b21206a2 | ||
|
|
96821c0026 | ||
|
|
3d5e4754af | ||
|
|
613e9f0788 | ||
|
|
1261247e94 | ||
|
|
2ad843ffbb | ||
|
|
6923b97cd2 | ||
|
|
e639237088 | ||
|
|
5bc1fbb8c2 | ||
|
|
e310931417 | ||
|
|
17944311c3 | ||
|
|
d82f87aa2a | ||
|
|
6aa21aceb8 | ||
|
|
870ad57a7e | ||
|
|
fddee71829 | ||
|
|
6f15a5ab0f | ||
|
|
1fd64c5d7b | ||
|
|
f8614f11ff | ||
|
|
5e5e77236a | ||
|
|
4cee53b07c | ||
|
|
c289fac008 | ||
|
|
68f9e86cdd | ||
|
|
5da754db65 | ||
|
|
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 |
@@ -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-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: []
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
**/node_modules
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
.next
|
||||||
|
**/.next
|
||||||
|
dist
|
||||||
|
**/dist
|
||||||
|
build
|
||||||
|
**/build
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
**/coverage
|
||||||
|
test-results
|
||||||
|
**/test-results
|
||||||
|
.turbo
|
||||||
|
**/.turbo
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Examples and docs
|
||||||
|
docs
|
||||||
+39
-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,10 @@ 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
|
||||||
|
|
||||||
|
# Force Node.js to use UTC as process timezone
|
||||||
|
TZ=UTC
|
||||||
|
|||||||
@@ -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,5 +0,0 @@
|
|||||||
// This configuration only applies to the package manager root.
|
|
||||||
/** @type {import("eslint").Linter.Config} */
|
|
||||||
module.exports = {
|
|
||||||
extends: ["./packages/config/eslint-preset.js"],
|
|
||||||
};
|
|
||||||
+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
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ runs:
|
|||||||
with:
|
with:
|
||||||
path: ${{ inputs.path }}
|
path: ${{ inputs.path }}
|
||||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
|
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
|
||||||
- run: echo ${{ env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }} && yarn db-seed
|
- run: USE_POOL=true yarn db-seed
|
||||||
if: steps.cache-db.outputs.cache-hit != 'true'
|
if: steps.cache-db.outputs.cache-hit != 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: Postgres Dump Backup
|
- name: Postgres Dump Backup
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
name: "Docker Build and Test"
|
||||||
|
description: "Reusable action to build and test Docker images for Cal.com"
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
platform:
|
||||||
|
description: "Target platform (linux/amd64 or arm64)"
|
||||||
|
required: true
|
||||||
|
platform-suffix:
|
||||||
|
description: "Suffix to add to image tags (e.g., -arm)"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
dockerhub-username:
|
||||||
|
description: "Docker Hub username"
|
||||||
|
required: true
|
||||||
|
dockerhub-token:
|
||||||
|
description: "Docker Hub token"
|
||||||
|
required: true
|
||||||
|
github-token:
|
||||||
|
description: "GitHub token for GHCR"
|
||||||
|
required: true
|
||||||
|
postgres-user:
|
||||||
|
description: "PostgreSQL user"
|
||||||
|
required: true
|
||||||
|
postgres-password:
|
||||||
|
description: "PostgreSQL password"
|
||||||
|
required: true
|
||||||
|
postgres-db:
|
||||||
|
description: "PostgreSQL database name"
|
||||||
|
required: true
|
||||||
|
database-host:
|
||||||
|
description: "Database host"
|
||||||
|
required: true
|
||||||
|
push-image:
|
||||||
|
description: "Whether to push the built image"
|
||||||
|
required: false
|
||||||
|
default: "false"
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
steps:
|
||||||
|
- name: Log in to the Docker Hub registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ inputs.dockerhub-username }}
|
||||||
|
password: ${{ inputs.dockerhub-token }}
|
||||||
|
logout: true
|
||||||
|
|
||||||
|
- name: Log in to the Github Container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ inputs.github-token }}
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
docker.io/calendso/calendso
|
||||||
|
docker.io/calcom/cal.com
|
||||||
|
ghcr.io/calcom/cal.com
|
||||||
|
flavor: |
|
||||||
|
latest=${{ !github.event.release.prerelease }}
|
||||||
|
suffix=${{ inputs.platform-suffix }}
|
||||||
|
|
||||||
|
- name: Copy env
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
grep -o '^[^#]*' .env.example > .env
|
||||||
|
cat .env >> $GITHUB_ENV
|
||||||
|
echo "DATABASE_HOST=localhost:5432" >> $GITHUB_ENV
|
||||||
|
eval $(sed -e '/^#/d' -e 's/^/export /' -e 's/$/;/' .env) ;
|
||||||
|
|
||||||
|
- name: Start database
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
docker compose up -d database
|
||||||
|
|
||||||
|
- name: Show database logs and container status
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "--- Container Status ---"
|
||||||
|
docker compose ps database
|
||||||
|
|
||||||
|
echo "--- Container Logs ---"
|
||||||
|
docker compose logs database
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
driver-opts: |
|
||||||
|
network=container:database
|
||||||
|
buildkitd-flags: |
|
||||||
|
--allow-insecure-entitlement security.insecure --allow-insecure-entitlement network.host
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./Dockerfile
|
||||||
|
load: true
|
||||||
|
push: false
|
||||||
|
platforms: ${{ inputs.platform }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
||||||
|
NEXT_PUBLIC_API_V2_URL=http://localhost:5555/api/v2
|
||||||
|
NEXT_PUBLIC_LICENSE_CONSENT=agree
|
||||||
|
DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
|
||||||
|
DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
|
||||||
|
|
||||||
|
- name: Test runtime
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
tags="${{ steps.meta.outputs.tags }}"
|
||||||
|
IFS=',' read -ra ADDR <<< "$tags"
|
||||||
|
tag=${ADDR[0]}
|
||||||
|
|
||||||
|
docker run --rm --network stack \
|
||||||
|
-p 3000:3000 \
|
||||||
|
-e DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@database/${{ inputs.postgres-db }} \
|
||||||
|
-e DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@database/${{ inputs.postgres-db }} \
|
||||||
|
-e NEXTAUTH_SECRET=${{ env.NEXTAUTH_SECRET }} \
|
||||||
|
-e CALENDSO_ENCRYPTION_KEY=${{ env.CALENDSO_ENCRYPTION_KEY }} \
|
||||||
|
$tag &
|
||||||
|
|
||||||
|
server_pid=$!
|
||||||
|
|
||||||
|
echo "Waiting for the server to start..."
|
||||||
|
sleep 120
|
||||||
|
|
||||||
|
echo http://localhost:3000/auth/login
|
||||||
|
|
||||||
|
for i in {1..60}; do
|
||||||
|
echo "Checking server health ($i/60)..."
|
||||||
|
response=$(curl -o /dev/null -s -w "%{http_code}" http://localhost:3000/auth/login)
|
||||||
|
echo "HTTP Status Code: $response"
|
||||||
|
if [[ "$response" == "200" ]] || [[ "$response" == "307" ]]; then
|
||||||
|
echo "Server is healthy"
|
||||||
|
kill $server_pid
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Server health check failed"
|
||||||
|
kill $server_pid
|
||||||
|
exit 1
|
||||||
|
env:
|
||||||
|
NEXTAUTH_SECRET: "EI4qqDpcfdvf4A+0aQEEx8JjHxHSy4uWiZw/F32K+pA="
|
||||||
|
CALENDSO_ENCRYPTION_KEY: "0zfLtY99wjeLnsM7qsa8xsT+Q0oSgnOL"
|
||||||
|
|
||||||
|
- name: Push image
|
||||||
|
id: docker_push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
if: ${{ inputs.push-image == 'true' && !github.event.release.prerelease }}
|
||||||
|
with:
|
||||||
|
context: ./
|
||||||
|
file: ./Dockerfile
|
||||||
|
push: true
|
||||||
|
platforms: ${{ inputs.platform }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
||||||
|
NEXT_PUBLIC_API_V2_URL=http://localhost:5555/api/v2
|
||||||
|
NEXT_PUBLIC_LICENSE_CONSENT=agree
|
||||||
|
DATABASE_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
|
||||||
|
DATABASE_DIRECT_URL=postgresql://${{ inputs.postgres-user }}:${{ inputs.postgres-password }}@${{ inputs.database-host }}/${{ inputs.postgres-db }}
|
||||||
|
|
||||||
|
- name: Image digest
|
||||||
|
shell: bash
|
||||||
|
run: echo ${{ steps.docker_build.outputs.digest }}
|
||||||
|
|
||||||
|
- name: Cleanup
|
||||||
|
shell: bash
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose down
|
||||||
@@ -56,8 +56,8 @@ jobs:
|
|||||||
if: steps.cache-api-v2-build.outputs.cache-hit != 'true'
|
if: steps.cache-api-v2-build.outputs.cache-hit != 'true'
|
||||||
run: |
|
run: |
|
||||||
export NODE_OPTIONS="--max_old_space_size=8192"
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
yarn workspace @calcom/api-v2 run generate-schemas
|
yarn workspace @calcom/api-v2 run generate-schemas &&
|
||||||
rm -rf apps/api/v2/node_modules
|
rm -rf apps/api/v2/node_modules &&
|
||||||
yarn install
|
yarn install &&
|
||||||
yarn workspace @calcom/api-v2 run build
|
yarn workspace @calcom/api-v2 run build
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -6,23 +6,31 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||||
permissions: {} # remove permissions and set explicitly under "permissions:" below
|
permissions:
|
||||||
|
id-token: write # for NPM provenance - developers can verify where and how it was built
|
||||||
|
contents: write # to create release by changesets/action below
|
||||||
|
pull-requests: write # to create pull request by changesets/action below
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
if: github.repository == 'calcom/cal.com' # prevent this action from running on forks
|
if: github.repository == 'calcom/cal.com' # prevent this action from running on forks
|
||||||
permissions:
|
|
||||||
id-token: write # for NPM provenance - developers can verify where and how it was built
|
|
||||||
contents: write # to create release by changesets/action below
|
|
||||||
pull-requests: write # to create pull request by changesets/action below
|
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repo
|
- name: Checkout Repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- name: Setup Node and registry for OIDC
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20.17.0"
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
always-auth: false
|
||||||
- name: Setup node, yarn and install dependencies
|
- name: Setup node, yarn and install dependencies
|
||||||
uses: ./.github/actions/yarn-install
|
uses: ./.github/actions/yarn-install
|
||||||
|
- name: Install npm at version supporting OIDC
|
||||||
|
run: npm install -g npm@11.5.1
|
||||||
- name: Create Release Pull Request or Publish to npm
|
- name: Create Release Pull Request or Publish to npm
|
||||||
id: changesets
|
id: changesets
|
||||||
uses: changesets/action@v1
|
uses: changesets/action@v1
|
||||||
@@ -33,8 +41,7 @@ jobs:
|
|||||||
title: "chore: version packages" # PR title
|
title: "chore: version packages" # PR title
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_CONFIG_PROVENANCE: "true"
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
VITE_BOOKER_EMBED_OAUTH_CLIENT_ID: ${{ secrets.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID }}
|
VITE_BOOKER_EMBED_OAUTH_CLIENT_ID: ${{ secrets.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID }}
|
||||||
VITE_BOOKER_EMBED_API_URL: ${{ secrets.VITE_BOOKER_EMBED_API_URL }}
|
VITE_BOOKER_EMBED_API_URL: ${{ secrets.VITE_BOOKER_EMBED_API_URL }}
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ secrets.NEXT_PUBLIC_WEBAPP_URL }}
|
NEXT_PUBLIC_WEBAPP_URL: ${{ secrets.NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ jobs:
|
|||||||
days-before-close: -1
|
days-before-close: -1
|
||||||
days-before-issue-stale: 60
|
days-before-issue-stale: 60
|
||||||
days-before-issue-close: -1
|
days-before-issue-close: -1
|
||||||
days-before-pr-stale: 14
|
days-before-pr-stale: 7
|
||||||
days-before-pr-close: -1
|
days-before-pr-close: 14
|
||||||
stale-pr-message: "This PR is being marked as stale due to inactivity."
|
stale-pr-message: "This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active."
|
||||||
close-pr-message: "This PR is being closed due to inactivity. Please reopen if work is intended to be continued."
|
close-pr-message: "This PR has been closed due to inactivity. Please feel free to reopen it if you'd like to continue the work."
|
||||||
operations-per-run: 100
|
operations-per-run: 100
|
||||||
|
|||||||
@@ -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:
|
||||||
@@ -23,6 +23,10 @@ env:
|
|||||||
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 }}
|
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
|
||||||
@@ -68,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
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
name: Atoms E2E Tests
|
name: E2E Atoms
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_call:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'packages/platform/atoms/**'
|
|
||||||
- 'packages/platform/examples/base/**'
|
|
||||||
- 'apps/api/v2/**'
|
|
||||||
- '.github/workflows/atoms-e2e.yml'
|
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
contents: read
|
contents: read
|
||||||
@@ -21,13 +13,15 @@ env:
|
|||||||
ATOMS_E2E_API_URL: ${{ secrets.ATOMS_E2E_API_URL }}
|
ATOMS_E2E_API_URL: ${{ secrets.ATOMS_E2E_API_URL }}
|
||||||
ATOMS_E2E_ORG_ID: ${{ secrets.ATOMS_E2E_ORG_ID }}
|
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_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_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
atoms-e2e:
|
e2e-atoms:
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
name: Atoms E2E Tests
|
name: E2E Atoms
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: docker/login-action@v3
|
- uses: docker/login-action@v3
|
||||||
@@ -38,20 +32,20 @@ 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/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
- name: Run Atoms E2E Tests
|
- name: Run E2E Atoms Tests
|
||||||
working-directory: packages/platform/examples/base
|
working-directory: packages/platform/examples/base
|
||||||
run: yarn test:e2e
|
run: yarn test:e2e
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: atoms-e2e-test-results
|
name: e2e-atoms-test-results
|
||||||
path: test-results
|
path: test-results
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
- name: Upload Playwright Report
|
- name: Upload Playwright Report
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: atoms-e2e-playwright-report
|
name: e2e-atoms-playwright-report
|
||||||
path: playwright-report
|
path: playwright-report
|
||||||
retention-days: 7
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ env:
|
|||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ env:
|
|||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
@@ -36,7 +35,6 @@ env:
|
|||||||
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Web App
|
name: Build Web App
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
name: "Release Docker"
|
name: "Release Docker"
|
||||||
|
|
||||||
on: # yamllint disable-line rule:truthy
|
env:
|
||||||
release:
|
POSTGRES_USER: "unicorn_user"
|
||||||
types:
|
POSTGRES_PASSWORD: "magical_password"
|
||||||
- created
|
POSTGRES_DB: "calendso"
|
||||||
- published
|
DATABASE_HOST: "database:5432"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
# in case manual trigger is needed
|
# in case manual trigger is needed
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
@@ -12,11 +17,9 @@ on: # yamllint disable-line rule:truthy
|
|||||||
description: "v{Major}.{Minor}.{Patch}"
|
description: "v{Major}.{Minor}.{Patch}"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release-amd64:
|
||||||
name: "Remote Release"
|
name: "Release AMD64"
|
||||||
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
runs-on: "ubuntu-latest"
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -24,25 +27,84 @@ jobs:
|
|||||||
- name: "Determine tag"
|
- name: "Determine tag"
|
||||||
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
|
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
|
||||||
|
|
||||||
- name: "Run remote release workflow"
|
- name: Build and test Docker image
|
||||||
uses: "actions/github-script@v6"
|
uses: ./.github/actions/docker-build-and-test
|
||||||
with:
|
with:
|
||||||
# Requires a personal access token with Actions Read and write permissions on calcom/docker.
|
platform: "linux/amd64"
|
||||||
github-token: "${{ secrets.DOCKER_REPO_ACCESS_TOKEN }}"
|
platform-suffix: ""
|
||||||
script: |
|
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
try {
|
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
const response = await github.rest.actions.createWorkflowDispatch({
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
owner: context.repo.owner,
|
postgres-user: ${{ env.POSTGRES_USER }}
|
||||||
repo: 'docker',
|
postgres-password: ${{ env.POSTGRES_PASSWORD }}
|
||||||
workflow_id: 'create-release.yaml',
|
postgres-db: ${{ env.POSTGRES_DB }}
|
||||||
ref: 'main',
|
database-host: ${{ env.DATABASE_HOST }}
|
||||||
inputs: {
|
push-image: "true"
|
||||||
"RELEASE_TAG": process.env.RELEASE_TAG
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(response);
|
- name: Notify Slack on Success
|
||||||
} catch (error) {
|
if: success()
|
||||||
console.error(error);
|
uses: slackapi/slack-github-action@v1.24.0
|
||||||
core.setFailed(error.message);
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": ":large_green_circle: Workflow *${{ github.workflow }}* (AMD64) succeeded in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
}
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
|
||||||
|
|
||||||
|
- name: Notify Slack on Failure
|
||||||
|
if: failure()
|
||||||
|
uses: slackapi/slack-github-action@v1.24.0
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": ":red_circle: Workflow *${{ github.workflow }}* (AMD64) failed in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
|
||||||
|
|
||||||
|
release-arm:
|
||||||
|
name: "Release ARM"
|
||||||
|
runs-on: ubuntu-24.04-arm
|
||||||
|
steps:
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: "Determine tag"
|
||||||
|
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
|
||||||
|
|
||||||
|
- name: Build and test Docker image
|
||||||
|
uses: ./.github/actions/docker-build-and-test
|
||||||
|
with:
|
||||||
|
platform: "arm64"
|
||||||
|
platform-suffix: "-arm"
|
||||||
|
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
postgres-user: ${{ env.POSTGRES_USER }}
|
||||||
|
postgres-password: ${{ env.POSTGRES_PASSWORD }}
|
||||||
|
postgres-db: ${{ env.POSTGRES_DB }}
|
||||||
|
database-host: ${{ env.DATABASE_HOST }}
|
||||||
|
push-image: "true"
|
||||||
|
|
||||||
|
- name: Notify Slack on Success
|
||||||
|
if: success()
|
||||||
|
uses: slackapi/slack-github-action@v1.24.0
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": ":large_green_circle: Workflow *${{ github.workflow }}* (ARM) succeeded in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
|
||||||
|
|
||||||
|
- name: Notify Slack on Failure
|
||||||
|
if: failure()
|
||||||
|
uses: slackapi/slack-github-action@v1.24.0
|
||||||
|
with:
|
||||||
|
payload: |
|
||||||
|
{
|
||||||
|
"text": ":red_circle: Workflow *${{ github.workflow }}* (ARM) failed in job *${{ github.job }}*.\nSee: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
}
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.CI_SLACK_WEBHOOK_URL }}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
+9
-1
@@ -66,7 +66,11 @@ tsconfig.tsbuildinfo
|
|||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
# Prisma generated files
|
# Prisma generated files
|
||||||
packages/prisma/zod/*.ts
|
packages/prisma/client/*
|
||||||
|
!packages/prisma/client/index.ts
|
||||||
|
apps/api/v2/generated/prisma/*
|
||||||
|
packages/prisma/generated/prisma/*
|
||||||
|
packages/prisma/zod/**/*.ts
|
||||||
packages/prisma/enums
|
packages/prisma/enums
|
||||||
|
|
||||||
# Builds
|
# Builds
|
||||||
@@ -97,3 +101,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
File diff suppressed because one or more lines are too long
Vendored
Vendored
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
Vendored
Vendored
Vendored
Vendored
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
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
Vendored
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
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
|
- "@calcom/prisma"
|
||||||
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
undecided:
|
||||||
|
- calcom-monorepo
|
||||||
@@ -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
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
FROM --platform=$BUILDPLATFORM node:20 AS builder
|
||||||
|
|
||||||
|
WORKDIR /calcom
|
||||||
|
|
||||||
|
## If we want to read any ENV variable from .env file, we need to first accept and pass it as an argument to the Dockerfile
|
||||||
|
ARG NEXT_PUBLIC_LICENSE_CONSENT
|
||||||
|
ARG NEXT_PUBLIC_WEBSITE_TERMS_URL
|
||||||
|
ARG NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL
|
||||||
|
ARG CALCOM_TELEMETRY_DISABLED
|
||||||
|
ARG DATABASE_URL
|
||||||
|
ARG NEXTAUTH_SECRET=secret
|
||||||
|
ARG CALENDSO_ENCRYPTION_KEY=secret
|
||||||
|
ARG MAX_OLD_SPACE_SIZE=4096
|
||||||
|
ARG NEXT_PUBLIC_API_V2_URL
|
||||||
|
ARG CSP_POLICY
|
||||||
|
|
||||||
|
## We need these variables as required by Next.js build to create rewrites
|
||||||
|
ARG NEXT_PUBLIC_SINGLE_ORG_SLUG
|
||||||
|
ARG ORGANIZATIONS_ENABLED
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_WEBAPP_URL=http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER \
|
||||||
|
NEXT_PUBLIC_API_V2_URL=$NEXT_PUBLIC_API_V2_URL \
|
||||||
|
NEXT_PUBLIC_LICENSE_CONSENT=$NEXT_PUBLIC_LICENSE_CONSENT \
|
||||||
|
NEXT_PUBLIC_WEBSITE_TERMS_URL=$NEXT_PUBLIC_WEBSITE_TERMS_URL \
|
||||||
|
NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL=$NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL \
|
||||||
|
CALCOM_TELEMETRY_DISABLED=$CALCOM_TELEMETRY_DISABLED \
|
||||||
|
DATABASE_URL=$DATABASE_URL \
|
||||||
|
DATABASE_DIRECT_URL=$DATABASE_URL \
|
||||||
|
NEXTAUTH_SECRET=${NEXTAUTH_SECRET} \
|
||||||
|
CALENDSO_ENCRYPTION_KEY=${CALENDSO_ENCRYPTION_KEY} \
|
||||||
|
NEXT_PUBLIC_SINGLE_ORG_SLUG=$NEXT_PUBLIC_SINGLE_ORG_SLUG \
|
||||||
|
ORGANIZATIONS_ENABLED=$ORGANIZATIONS_ENABLED \
|
||||||
|
NODE_OPTIONS=--max-old-space-size=${MAX_OLD_SPACE_SIZE} \
|
||||||
|
BUILD_STANDALONE=true \
|
||||||
|
CSP_POLICY=$CSP_POLICY
|
||||||
|
|
||||||
|
COPY package.json yarn.lock .yarnrc.yml playwright.config.ts turbo.json i18n.json ./
|
||||||
|
COPY .yarn ./.yarn
|
||||||
|
COPY apps/web ./apps/web
|
||||||
|
COPY apps/api/v2 ./apps/api/v2
|
||||||
|
COPY packages ./packages
|
||||||
|
COPY tests ./tests
|
||||||
|
|
||||||
|
RUN yarn config set httpTimeout 1200000
|
||||||
|
RUN npx turbo prune --scope=@calcom/web --scope=@calcom/trpc --docker
|
||||||
|
RUN yarn install
|
||||||
|
# Build and make embed servable from web/public/embed folder
|
||||||
|
RUN yarn workspace @calcom/trpc run build
|
||||||
|
RUN yarn --cwd packages/embeds/embed-core workspace @calcom/embed-core run build
|
||||||
|
RUN yarn --cwd apps/web workspace @calcom/web run build
|
||||||
|
RUN rm -rf node_modules/.cache .yarn/cache apps/web/.next/cache
|
||||||
|
|
||||||
|
FROM node:20 AS builder-two
|
||||||
|
|
||||||
|
WORKDIR /calcom
|
||||||
|
ARG NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
COPY package.json .yarnrc.yml turbo.json i18n.json ./
|
||||||
|
COPY .yarn ./.yarn
|
||||||
|
COPY --from=builder /calcom/yarn.lock ./yarn.lock
|
||||||
|
COPY --from=builder /calcom/node_modules ./node_modules
|
||||||
|
COPY --from=builder /calcom/packages ./packages
|
||||||
|
COPY --from=builder /calcom/apps/web ./apps/web
|
||||||
|
COPY --from=builder /calcom/packages/prisma/schema.prisma ./prisma/schema.prisma
|
||||||
|
COPY scripts scripts
|
||||||
|
RUN chmod +x scripts/*
|
||||||
|
|
||||||
|
# Save value used during this build stage. If NEXT_PUBLIC_WEBAPP_URL and BUILT_NEXT_PUBLIC_WEBAPP_URL differ at
|
||||||
|
# run-time, then start.sh will find/replace static values again.
|
||||||
|
ENV NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL \
|
||||||
|
BUILT_NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL
|
||||||
|
|
||||||
|
RUN scripts/replace-placeholder.sh http://NEXT_PUBLIC_WEBAPP_URL_PLACEHOLDER ${NEXT_PUBLIC_WEBAPP_URL}
|
||||||
|
|
||||||
|
FROM node:20 AS runner
|
||||||
|
|
||||||
|
WORKDIR /calcom
|
||||||
|
COPY --from=builder-two /calcom ./
|
||||||
|
ARG NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
||||||
|
ENV NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL \
|
||||||
|
BUILT_NEXT_PUBLIC_WEBAPP_URL=$NEXT_PUBLIC_WEBAPP_URL
|
||||||
|
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=30s --retries=5 \
|
||||||
|
CMD wget --spider http://localhost:3000 || exit 1
|
||||||
|
|
||||||
|
CMD ["/calcom/scripts/start.sh"]
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<img src="https://user-images.githubusercontent.com/8019099/210054112-5955e812-a76e-4160-9ddd-58f2c72f1cce.png" alt="Logo">
|
<img src="https://user-images.githubusercontent.com/8019099/210054112-5955e812-a76e-4160-9ddd-58f2c72f1cce.png" alt="Logo">
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<h3 align="center">Cal.com (formerly Calendso)</h3>
|
<h3 align="center">Cal.com</h3>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
The open-source Calendly successor.
|
The open-source Calendly successor.
|
||||||
@@ -175,6 +175,7 @@ yarn dx
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Development tip
|
#### Development tip
|
||||||
|
|
||||||
1. Add `export NODE_OPTIONS=“--max-old-space-size=16384”` to your shell script to increase the memory limit for the node process. Alternatively, you can run this in your terminal before running the app. Replace 16384 with the amount of RAM you want to allocate to the node process.
|
1. Add `export NODE_OPTIONS=“--max-old-space-size=16384”` to your shell script to increase the memory limit for the node process. Alternatively, you can run this in your terminal before running the app. Replace 16384 with the amount of RAM you want to allocate to the node process.
|
||||||
|
|
||||||
2. Add `NEXT_PUBLIC_LOGGER_LEVEL={level}` to your .env file to control the logging verbosity for all tRPC queries and mutations.\
|
2. Add `NEXT_PUBLIC_LOGGER_LEVEL={level}` to your .env file to control the logging verbosity for all tRPC queries and mutations.\
|
||||||
@@ -226,7 +227,7 @@ for Logger level to be set at info, for example.
|
|||||||
|
|
||||||
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
|
3. Now open your psql shell with the DB you created: `psql -h localhost -U postgres -d <DB name>`
|
||||||
|
|
||||||
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
|
4. Inside the psql shell execute `\conninfo`. And you will get the following info.
|
||||||

|

|
||||||
|
|
||||||
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
5. Now extract all the info and add it to your DATABASE_URL. The url would look something like this
|
||||||
@@ -234,9 +235,10 @@ for Logger level to be set at info, for example.
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
If you don't want to create a local DB. Then you can also consider using services like railway.app or render.
|
If you don't want to create a local DB. Then you can also consider using services like railway.app, Northflank or render.
|
||||||
|
|
||||||
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
- [Setup postgres DB with railway.app](https://docs.railway.app/guides/postgresql)
|
||||||
|
- [Setup postgres DB with Northflank](https://northflank.com/guides/deploy-postgres-database-on-northflank)
|
||||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||||
|
|
||||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||||
@@ -374,15 +376,268 @@ Executable doesn't exist at /Users/alice/Library/Caches/ms-playwright/chromium-1
|
|||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
The Docker configuration for Cal.com is an effort powered by people within the community.
|
**Official support**: Our team will begin to officially support the Dockerfile and docker-compose resources in this
|
||||||
|
repository.
|
||||||
|
|
||||||
If you want to contribute to the Docker repository, [reply here](https://github.com/calcom/docker/discussions/32).
|
**Important**: Cal.com will **not** be supporting installations that use these Docker resources. While we provide and maintain the Docker configurations, support for Docker-based installations is the responsibility of the user.
|
||||||
|
|
||||||
The Docker configuration can be found [in our docker repository](https://github.com/calcom/docker).
|
This image can be found on DockerHub at [https://hub.docker.com/r/calcom/cal.com](https://hub.docker.com/r/calcom/cal.com).
|
||||||
|
|
||||||
Issues with Docker? Find your answer or open a new discussion [here](https://github.com/calcom/docker/discussions) to ask the community.
|
**Note for ARM Users**: Use the {version}-arm suffix for pulling images. Example: `docker pull calcom/cal.com:v5.6.19-arm`.
|
||||||
|
|
||||||
Cal.com, Inc. does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
|
#### Requirements
|
||||||
|
|
||||||
|
Make sure you have `docker` & `docker compose` installed on the server / system. Both are installed by most docker utilities, including Docker Desktop and Rancher Desktop.
|
||||||
|
|
||||||
|
Note: `docker compose` without the hyphen is now the primary method of using docker-compose, per the Docker documentation.
|
||||||
|
|
||||||
|
#### (Most users) Running Cal.com with Docker Compose
|
||||||
|
|
||||||
|
If you are evaluating Cal.com or running with minimal to no modifications, this option is for you.
|
||||||
|
|
||||||
|
1. Clone calcom/cal.com
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --recursive https://github.com/calcom/cal.com.git
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Change into the directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd cal.com
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Prepare your configuration: Rename `.env.example` to `.env` and then update `.env`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Most configurations can be left as-is, but for configuration options see [Important Run-time variables](#important-run-time-variables) below.
|
||||||
|
|
||||||
|
**Push Notifications (VAPID Keys)**
|
||||||
|
If you see an error like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Error: No key set vapidDetails.publicKey
|
||||||
|
```
|
||||||
|
|
||||||
|
This means your environment variables for Web Push are missing.
|
||||||
|
You must generate and set `NEXT_PUBLIC_VAPID_PUBLIC_KEY` and `VAPID_PRIVATE_KEY`.
|
||||||
|
|
||||||
|
Generate them with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx web-push generate-vapid-keys
|
||||||
|
```
|
||||||
|
|
||||||
|
Then update your `.env` file:
|
||||||
|
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_VAPID_PUBLIC_KEY=your_public_key_here
|
||||||
|
VAPID_PRIVATE_KEY=your_private_key_here
|
||||||
|
```
|
||||||
|
|
||||||
|
Do **not** commit real keys to `.env.example` — only placeholders.
|
||||||
|
|
||||||
|
Update the appropriate values in your .env file, then proceed.
|
||||||
|
|
||||||
|
4. (optional) Pre-Pull the images by running the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull
|
||||||
|
```
|
||||||
|
|
||||||
|
This will use the default image locations as specified by `image:` in the docker-compose.yaml file.
|
||||||
|
|
||||||
|
Note: To aid with support, by default Scarf.sh is used as registry proxy for download metrics.
|
||||||
|
|
||||||
|
5. Start Cal.com via docker compose
|
||||||
|
|
||||||
|
(Most basic users, and for First Run) To run the complete stack, which includes a local Postgres database, Cal.com web app, and Prisma Studio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To run Cal.com web app and Prisma Studio against a remote database, ensure that DATABASE_URL is configured for an available database and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d calcom studio
|
||||||
|
```
|
||||||
|
|
||||||
|
To run only the Cal.com web app, ensure that DATABASE_URL is configured for an available database and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d calcom
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note: to run in attached mode for debugging, remove `-d` from your desired run command.**
|
||||||
|
|
||||||
|
6. Open a browser to [http://localhost:3000](http://localhost:3000), or your defined NEXT_PUBLIC_WEBAPP_URL. The first time you run Cal.com, a setup wizard will initialize. Define your first user, and you're ready to go!
|
||||||
|
|
||||||
|
#### Updating Cal.com
|
||||||
|
|
||||||
|
1. Stop the Cal.com stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Pull the latest changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose pull
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update env vars as necessary.
|
||||||
|
4. Re-start the Cal.com stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### (Advanced users) Build and Run Cal.com
|
||||||
|
|
||||||
|
1. Clone calcom/docker.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/calcom/cal.com.git calcom-docker
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Change into the directory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd calcom-docker
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Update the calcom submodule. This project depends on the Cal.com source code, which is included here as a Git submodule. To make sure you get everything you need, update the submodule with the command below.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git submodule update --remote --init
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: DO NOT use recursive submodule update, otherwise you will receive a git authentication error.
|
||||||
|
|
||||||
|
4. Rename `.env.example` to `.env` and then update `.env`
|
||||||
|
|
||||||
|
For configuration options see [Build-time variables](#build-time-variables) below. Update the appropriate values in your .env file, then proceed.
|
||||||
|
|
||||||
|
5. Build the Cal.com docker image:
|
||||||
|
|
||||||
|
Note: Due to application configuration requirements, an available database is currently required during the build process.
|
||||||
|
|
||||||
|
a) If hosting elsewhere, configure the `DATABASE_URL` in the .env file, and skip the next step
|
||||||
|
|
||||||
|
b) If a local or temporary database is required, start a local database via docker compose.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d database
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Build Cal.com via docker compose (DOCKER_BUILDKIT=0 must be provided to allow a network bridge to be used at build time. This requirement will be removed in the future)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DOCKER_BUILDKIT=0 docker compose build calcom
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Start Cal.com via docker compose
|
||||||
|
|
||||||
|
(Most basic users, and for First Run) To run the complete stack, which includes a local Postgres database, Cal.com web app, and Prisma Studio:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
To run Cal.com web app and Prisma Studio against a remote database, ensure that DATABASE_URL is configured for an available database and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d calcom studio
|
||||||
|
```
|
||||||
|
|
||||||
|
To run only the Cal.com web app, ensure that DATABASE_URL is configured for an available database and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d calcom
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note: to run in attached mode for debugging, remove `-d` from your desired run command.**
|
||||||
|
|
||||||
|
8. Open a browser to [http://localhost:3000](http://localhost:3000), or your defined NEXT_PUBLIC_WEBAPP_URL. The first time you run Cal.com, a setup wizard will initialize. Define your first user, and you're ready to go!
|
||||||
|
|
||||||
|
#### Configuration
|
||||||
|
|
||||||
|
##### Important Run-time variables
|
||||||
|
|
||||||
|
These variables must also be provided at runtime
|
||||||
|
|
||||||
|
| Variable | Description | Required | Default |
|
||||||
|
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------- |
|
||||||
|
| DATABASE_URL | database url with credentials - if using a connection pooler, this setting should point there | required | `postgresql://unicorn_user:magical_password@database:5432/calendso` |
|
||||||
|
| CALCOM_LICENSE_KEY | Enterprise License Key | optional | |
|
||||||
|
| NEXT_PUBLIC_WEBAPP_URL | Base URL of the site. NOTE: if this value differs from the value used at build-time, there will be a slight delay during container start (to update the statically built files). | optional | `http://localhost:3000` |
|
||||||
|
| NEXTAUTH_URL | Location of the auth server. By default, this is the Cal.com docker instance itself. | optional | `{NEXT_PUBLIC_WEBAPP_URL}/api/auth` |
|
||||||
|
| NEXTAUTH_SECRET | must match build variable | required | `secret` |
|
||||||
|
| CALENDSO_ENCRYPTION_KEY | must match build variable | required | `secret` |
|
||||||
|
|
||||||
|
##### Build-time variables
|
||||||
|
|
||||||
|
If building the image yourself, these variables must be provided at the time of the docker build, and can be provided by updating the .env file. Currently, if you require changes to these variables, you must follow the instructions to build and publish your own image.
|
||||||
|
|
||||||
|
Updating these variables is not required for evaluation, but is required for running in production. Instructions for generating variables can be found in the [Cal.com instructions](https://github.com/calcom/cal.com)
|
||||||
|
|
||||||
|
| Variable | Description | Required | Default |
|
||||||
|
| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------- |
|
||||||
|
| DATABASE_URL | database url with credentials - if using a connection pooler, this setting should point there | required | `postgresql://unicorn_user:magical_password@database:5432/calendso` |
|
||||||
|
| MAX_OLD_SPACE_SIZE | Needed for Nodejs/NPM build options | required | 4096 |
|
||||||
|
| NEXT_PUBLIC_LICENSE_CONSENT | license consent - true/false | required | |
|
||||||
|
| NEXTAUTH_SECRET | Cookie encryption key | required | `secret` |
|
||||||
|
| CALENDSO_ENCRYPTION_KEY | Authentication encryption key | required | `secret` |
|
||||||
|
| NEXT_PUBLIC_WEBAPP_URL | Base URL injected into static files | optional | `http://localhost:3000` |
|
||||||
|
| NEXT_PUBLIC_WEBSITE_TERMS_URL | custom URL for terms and conditions website | optional | `https://cal.com/terms` |
|
||||||
|
| NEXT_PUBLIC_WEBSITE_PRIVACY_POLICY_URL | custom URL for privacy policy website | optional | `https://cal.com/privacy` |
|
||||||
|
| NEXT_PUBLIC_API_V2_URL | URL for the v2 API, only required for custom integrations or custom booking experiences using [Cal.com Platform](https://cal.com/platform) | optional | |
|
||||||
|
| CALCOM_TELEMETRY_DISABLED | Allow Cal.com to collect anonymous usage data (set to `1` to disable) | optional | |
|
||||||
|
| NEXT_PUBLIC_SINGLE_ORG_SLUG | Required if ORGANIZATIONS_ENABLED is true | optional | |
|
||||||
|
| ORGANIZATIONS_ENABLED | Used for Enterprise or Organizations plan | optional | |
|
||||||
|
|
||||||
|
#### Troubleshooting
|
||||||
|
|
||||||
|
##### SSL edge termination
|
||||||
|
|
||||||
|
If running behind a load balancer which handles SSL certificates, you will need to add the environmental variable `NODE_TLS_REJECT_UNAUTHORIZED=0` to prevent requests from being rejected. Only do this if you know what you are doing and trust the services/load-balancers directing traffic to your service.
|
||||||
|
|
||||||
|
##### Failed to commit changes: Invalid 'prisma.user.create()'
|
||||||
|
|
||||||
|
Certain versions may have trouble creating a user if the field `metadata` is empty. Using an empty json object `{}` as the field value should resolve this issue. Also, the `id` field will autoincrement, so you may also try leaving the value of `id` as empty.
|
||||||
|
|
||||||
|
##### CLIENT_FETCH_ERROR
|
||||||
|
|
||||||
|
If you experience this error, it may be the way the default Auth callback in the server is using the WEBAPP_URL as a base url. The container does not necessarily have access to the same DNS as your local machine, and therefor needs to be configured to resolve to itself. You may be able to correct this by configuring `NEXTAUTH_URL=http://localhost:3000/api/auth`, to help the backend loop back to itself.
|
||||||
|
|
||||||
|
```
|
||||||
|
docker-calcom-1 | @calcom/web:start: [next-auth][error][CLIENT_FETCH_ERROR]
|
||||||
|
docker-calcom-1 | @calcom/web:start: https://next-auth.js.org/errors#client_fetch_error request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost {
|
||||||
|
docker-calcom-1 | @calcom/web:start: error: {
|
||||||
|
docker-calcom-1 | @calcom/web:start: message: 'request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost',
|
||||||
|
docker-calcom-1 | @calcom/web:start: stack: 'FetchError: request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at ClientRequest.<anonymous> (/calcom/node_modules/next/dist/compiled/node-fetch/index.js:1:65756)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at ClientRequest.emit (node:events:513:28)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at ClientRequest.emit (node:domain:489:12)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at Socket.socketErrorListener (node:_http_client:494:9)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at Socket.emit (node:events:513:28)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at Socket.emit (node:domain:489:12)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at emitErrorNT (node:internal/streams/destroy:157:8)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at emitErrorCloseNT (node:internal/streams/destroy:122:3)\n' +
|
||||||
|
docker-calcom-1 | @calcom/web:start: ' at processTicksAndRejections (node:internal/process/task_queues:83:21)',
|
||||||
|
docker-calcom-1 | @calcom/web:start: name: 'FetchError'
|
||||||
|
docker-calcom-1 | @calcom/web:start: },
|
||||||
|
docker-calcom-1 | @calcom/web:start: url: 'http://testing.localhost:3000/api/auth/session',
|
||||||
|
docker-calcom-1 | @calcom/web:start: message: 'request to http://testing.localhost:3000/api/auth/session failed, reason: getaddrinfo ENOTFOUND testing.localhost'
|
||||||
|
docker-calcom-1 | @calcom/web:start: }
|
||||||
|
```
|
||||||
|
|
||||||
|
<img referrerpolicy="no-referrer-when-downgrade" src="https://static.scarf.sh/a.png?x-pxid=81cda9f7-a102-453b-ac01-51c35650bd70" />
|
||||||
|
|
||||||
### Railway
|
### Railway
|
||||||
|
|
||||||
@@ -390,6 +645,12 @@ Cal.com, Inc. does not provide official support for Docker, but we will accept f
|
|||||||
|
|
||||||
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
You can deploy Cal.com on [Railway](https://railway.app) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal.com on their platform.
|
||||||
|
|
||||||
|
### Northflank
|
||||||
|
|
||||||
|
[](https://northflank.com/stacks/deploy-calcom)
|
||||||
|
|
||||||
|
You can deploy Cal.com on [Northflank](https://northflank.com) using the button above. The team at Northflank also have a [detailed blog post](https://northflank.com/guides/deploy-calcom-with-northflank) on deploying Cal.com on their platform.
|
||||||
|
|
||||||
### Vercel
|
### Vercel
|
||||||
|
|
||||||
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
|
Currently Vercel Pro Plan is required to be able to Deploy this application with Vercel, due to limitations on the number of serverless functions on the free plan.
|
||||||
@@ -418,7 +679,7 @@ See the [roadmap project](https://cal.com/roadmap) for a list of proposed featur
|
|||||||
|
|
||||||
Cal.com, Inc. is a commercial open source company, which means some parts of this open source repository require a commercial license. The concept is called "Open Core" where the core technology (99%) is fully open source, licensed under [AGPLv3](https://opensource.org/license/agpl-v3) and the last 1% is covered under a commercial license (["/ee" Enterprise Edition](https://github.com/calcom/cal.com/tree/main/packages/features/ee)) which we believe is entirely relevant for larger organisations that require enterprise features. Enterprise features are built by the core engineering team of Cal.com, Inc. which is hired in full-time. Find their compensation on https://cal.com/open.
|
Cal.com, Inc. is a commercial open source company, which means some parts of this open source repository require a commercial license. The concept is called "Open Core" where the core technology (99%) is fully open source, licensed under [AGPLv3](https://opensource.org/license/agpl-v3) and the last 1% is covered under a commercial license (["/ee" Enterprise Edition](https://github.com/calcom/cal.com/tree/main/packages/features/ee)) which we believe is entirely relevant for larger organisations that require enterprise features. Enterprise features are built by the core engineering team of Cal.com, Inc. which is hired in full-time. Find their compensation on https://cal.com/open.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All commercial "Multiplayer APIs" are under a commercial license.
|
> Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All commercial "Multiplayer APIs" are under a commercial license.
|
||||||
|
|
||||||
| | AGPLv3 | EE |
|
| | AGPLv3 | EE |
|
||||||
@@ -490,8 +751,8 @@ Don't code but still want to contribute? Join our [Discussions](https://github.c
|
|||||||
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
|
- Set CSP_POLICY="non-strict" env variable, which enables [Strict CSP](https://web.dev/strict-csp/) except for unsafe-inline in style-src . If you have some custom changes in your instance, you might have to make some code change to make your instance CSP compatible. Right now it enables strict CSP only on login page and on other SSR pages it is enabled in Report only mode to detect possible issues. On, SSG pages it is still not supported.
|
||||||
|
|
||||||
## Single Org Mode
|
## Single Org Mode
|
||||||
Refer to docs [here](./docs/self-hosting/guides/organization/single-organization-setup) for a detailed documentation with screenshots.
|
|
||||||
|
|
||||||
|
Refer to docs [here](./docs/self-hosting/guides/organization/single-organization-setup) for a detailed documentation with screenshots.
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
@@ -648,6 +909,7 @@ following
|
|||||||
We use changesets to generate changelogs and publish public packages (packages with `private: true` are ignored).
|
We use changesets to generate changelogs and publish public packages (packages with `private: true` are ignored).
|
||||||
|
|
||||||
An example of good readme is [atoms readme](https://github.com/calcom/cal.com/blob/main/packages/platform/atoms/README.md). Every public package must:
|
An example of good readme is [atoms readme](https://github.com/calcom/cal.com/blob/main/packages/platform/atoms/README.md). Every public package must:
|
||||||
|
|
||||||
1. Follow semantic versioning when using changesets.
|
1. Follow semantic versioning when using changesets.
|
||||||
2. Mark breaking changes using `❗️Breaking change`
|
2. Mark breaking changes using `❗️Breaking change`
|
||||||
|
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import { get } from "@vercel/edge-config";
|
|
||||||
import type { NextMiddleware } from "next-api-middleware";
|
|
||||||
|
|
||||||
const safeGet = async <T = unknown>(key: string): Promise<T | undefined> => {
|
|
||||||
try {
|
|
||||||
return get<T>(key);
|
|
||||||
} catch (error) {
|
|
||||||
// Don't crash if EDGE_CONFIG env var is missing
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const config = { matcher: "/:path*" };
|
|
||||||
|
|
||||||
export const checkIsInMaintenanceMode: NextMiddleware = async (req, res, next) => {
|
|
||||||
const isInMaintenanceMode = await safeGet<boolean>("isInMaintenanceMode");
|
|
||||||
if (isInMaintenanceMode) {
|
|
||||||
return res
|
|
||||||
.status(503)
|
|
||||||
.json({ message: "API is currently under maintenance. Please try again at a later time." });
|
|
||||||
}
|
|
||||||
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
@@ -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: {
|
role: UserPermissionRole.ADMIN,
|
||||||
email: "admin@example.com",
|
locked: false,
|
||||||
role: UserPermissionRole.ADMIN,
|
email: "admin@example.com",
|
||||||
locked: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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: {
|
role: UserPermissionRole.USER,
|
||||||
email: "org-admin@acme.com",
|
locked: false,
|
||||||
role: UserPermissionRole.USER,
|
email: "org-admin@acme.com",
|
||||||
locked: false,
|
|
||||||
teams: {
|
|
||||||
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: {
|
role: UserPermissionRole.USER,
|
||||||
email: "locked@example.com",
|
locked: true,
|
||||||
role: UserPermissionRole.USER,
|
email: "locked@example.com",
|
||||||
locked: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { label } from "next-api-middleware";
|
|||||||
import { addRequestId } from "./addRequestid";
|
import { addRequestId } from "./addRequestid";
|
||||||
import { captureErrors } from "./captureErrors";
|
import { captureErrors } from "./captureErrors";
|
||||||
import { captureUserId } from "./captureUserId";
|
import { captureUserId } from "./captureUserId";
|
||||||
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
|
|
||||||
import { extendRequest } from "./extendRequest";
|
import { extendRequest } from "./extendRequest";
|
||||||
import {
|
import {
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
@@ -26,7 +25,6 @@ const middleware = {
|
|||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
addRequestId,
|
addRequestId,
|
||||||
checkIsInMaintenanceMode,
|
|
||||||
verifyApiKey,
|
verifyApiKey,
|
||||||
rateLimitApiKey,
|
rateLimitApiKey,
|
||||||
extendRequest,
|
extendRequest,
|
||||||
@@ -38,16 +36,14 @@ const middleware = {
|
|||||||
|
|
||||||
type Middleware = keyof typeof middleware;
|
type Middleware = keyof typeof middleware;
|
||||||
|
|
||||||
const middlewareOrder = [
|
const middlewareOrder: Middleware[] = [
|
||||||
// The order here, determines the order of execution
|
|
||||||
"checkIsInMaintenanceMode",
|
|
||||||
"extendRequest",
|
"extendRequest",
|
||||||
"captureErrors",
|
"captureErrors",
|
||||||
"verifyApiKey",
|
"verifyApiKey",
|
||||||
"rateLimitApiKey",
|
"rateLimitApiKey",
|
||||||
"addRequestId",
|
"addRequestId",
|
||||||
"captureUserId",
|
"captureUserId",
|
||||||
] as Middleware[]; // <-- Provide a list of middleware to call automatically
|
];
|
||||||
|
|
||||||
const withMiddleware = label(middleware, middlewareOrder);
|
const withMiddleware = label(middleware, middlewareOrder);
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const getAllAdminMemberships = async (userId: number) => {
|
|||||||
where: {
|
where: {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
accepted: true,
|
accepted: true,
|
||||||
OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }],
|
role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] },
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
team: {
|
team: {
|
||||||
@@ -56,16 +56,19 @@ export const getAccessibleUsers = async ({
|
|||||||
memberUserIds,
|
memberUserIds,
|
||||||
adminUserId,
|
adminUserId,
|
||||||
}: AccessibleUsersType): Promise<number[]> => {
|
}: AccessibleUsersType): Promise<number[]> => {
|
||||||
|
const orConditions = [];
|
||||||
|
if (memberUserIds.length > 0) {
|
||||||
|
orConditions.push({ userId: { in: memberUserIds } });
|
||||||
|
}
|
||||||
|
orConditions.push({ userId: adminUserId, role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] } });
|
||||||
|
|
||||||
const memberships = await prisma.membership.findMany({
|
const memberships = await prisma.membership.findMany({
|
||||||
where: {
|
where: {
|
||||||
team: {
|
team: {
|
||||||
isOrganization: true,
|
isOrganization: true,
|
||||||
},
|
},
|
||||||
accepted: true,
|
accepted: true,
|
||||||
OR: [
|
OR: orConditions,
|
||||||
{ userId: { in: memberUserIds } },
|
|
||||||
{ userId: adminUserId, role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] } },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
userId: true,
|
userId: true,
|
||||||
|
|||||||
@@ -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,17 +70,16 @@ 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,
|
})
|
||||||
})
|
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
team: teamSchema.nullish(),
|
team: teamSchema.nullish(),
|
||||||
@@ -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(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user