Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b5fd593e | ||
|
|
6dd4933337 | ||
|
|
4217efc5a8 | ||
|
|
aca2b29aa4 | ||
|
|
a18807dbc2 | ||
|
|
bd1664ad6e | ||
|
|
23cbd838c2 | ||
|
|
a4a1c016dc | ||
|
|
1b176f0d49 | ||
|
|
99d4461dcc | ||
|
|
7d7e0ae6a7 | ||
|
|
c3638eb990 | ||
|
|
cfc157b665 | ||
|
|
96bf81ce8a | ||
|
|
0b9133c2cf | ||
|
|
bd6c471652 | ||
|
|
5ee3744015 | ||
|
|
98235d00c8 | ||
|
|
5c2d2deb82 | ||
|
|
36fa8854ad | ||
|
|
56022a1ba8 | ||
|
|
b67d37fac7 | ||
|
|
615a89737d | ||
|
|
d72c814999 | ||
|
|
124471ef4a | ||
|
|
96e93138e7 | ||
|
|
f28c2645db | ||
|
|
650399ddc7 | ||
|
|
64ad1c8d5b | ||
|
|
2714368a90 | ||
|
|
9bcd1d3609 | ||
|
|
b3fab3aacd | ||
|
|
e3741f3e06 | ||
|
|
ae4e0f8893 | ||
|
|
9c64e8dc28 | ||
|
|
bbb86f2174 | ||
|
|
a559daa076 | ||
|
|
e723b989a8 | ||
|
|
490540ac56 | ||
|
|
c25c835b76 | ||
|
|
ca05e701a8 | ||
|
|
4541f0d714 | ||
|
|
0ec5153f14 | ||
|
|
90ce0f8d4d | ||
|
|
5e9902921f | ||
|
|
5d3c72271b | ||
|
|
775010cd2d | ||
|
|
87a8b5a8dd | ||
|
|
973b624882 | ||
|
|
0a0c5ef61e | ||
|
|
46f67bba9b | ||
|
|
d9806da049 | ||
|
|
42fc49488d | ||
|
|
e030fe0e07 | ||
|
|
51428087ef | ||
|
|
3942df369d | ||
|
|
1d309a6f09 | ||
|
|
56f31eee7a | ||
|
|
2dae0bfb8c | ||
|
|
917c7b0764 | ||
|
|
fd64b0f468 | ||
|
|
b1909337db | ||
|
|
039bf6d4e3 | ||
|
|
83e15c4ce1 | ||
|
|
7d6ecf7201 | ||
|
|
bc42c27230 | ||
|
|
57abb26c31 | ||
|
|
a136511c8a | ||
|
|
c65dca9486 | ||
|
|
816d31918d | ||
|
|
097a69d7d7 | ||
|
|
c2a07e221d | ||
|
|
6d8fbfc49a | ||
|
|
304ef29618 | ||
|
|
b8890c841c | ||
|
|
2f5768b74f | ||
|
|
c1416173e8 | ||
|
|
726af4970c | ||
|
|
d15b7d8168 | ||
|
|
c5bd45fdfb | ||
|
|
5d7b2a12a7 | ||
|
|
7c90d1ed76 | ||
|
|
5ca10d1e83 | ||
|
|
f0e59c2331 | ||
|
|
f812d92045 | ||
|
|
23a17c4e8a | ||
|
|
5beb039003 | ||
|
|
63dd357664 | ||
|
|
b6a57381c7 | ||
|
|
7cf840723b | ||
|
|
bc0c40d260 | ||
|
|
89612a7a68 | ||
|
|
a2620a0e50 | ||
|
|
cfcf5b168c | ||
|
|
3f4d9962a7 | ||
|
|
83917e8655 | ||
|
|
f4bd0dbcc9 | ||
|
|
c87b8b43f9 | ||
|
|
b2baaea84c | ||
|
|
0e431ba7c6 | ||
|
|
b62f6eee3c | ||
|
|
2d0cac0dad | ||
|
|
0c3d1b3034 | ||
|
|
74f6d7686b | ||
|
|
49601bed6f | ||
|
|
b9659051d4 | ||
|
|
9c96507af2 | ||
|
|
2352babe93 | ||
|
|
1467815c3f | ||
|
|
7254cd97e2 | ||
|
|
4cb77abfd7 | ||
|
|
8deedfd361 | ||
|
|
3a0c766ed5 | ||
|
|
4f57e3201d | ||
|
|
e404f7eb88 | ||
|
|
5c2b561a5c | ||
|
|
57b404f721 | ||
|
|
df9a6621cf | ||
|
|
c478f73327 | ||
|
|
88f4dd2446 | ||
|
|
97b2816897 | ||
|
|
00372b6b3b | ||
|
|
6c8fced247 | ||
|
|
52813b01e6 | ||
|
|
77a98d96c8 | ||
|
|
676eac357f | ||
|
|
5bafd1b264 | ||
|
|
0391bfe887 | ||
|
|
c18407d75a | ||
|
|
a631286770 | ||
|
|
dd78741158 | ||
|
|
8f69811e25 | ||
|
|
3a63a8b228 | ||
|
|
ef98105503 | ||
|
|
a18f1eb382 | ||
|
|
2ab6315a8e | ||
|
|
258ebe0420 | ||
|
|
1cfd2f7053 | ||
|
|
a51024e915 | ||
|
|
900e90cd2c | ||
|
|
46ffeaee82 | ||
|
|
22425bb693 | ||
|
|
ad0b8a668c | ||
|
|
bbad2c9d31 | ||
|
|
8b28a6f5df | ||
|
|
93bb37702c | ||
|
|
4a8f251d89 | ||
|
|
cf44919cdd | ||
|
|
024e40b26b | ||
|
|
7c354321be | ||
|
|
fc16ec59d3 | ||
|
|
ab26481738 | ||
|
|
2fbc06314e | ||
|
|
386006fad0 | ||
|
|
ceb555460e | ||
|
|
c28870e029 | ||
|
|
0650898302 | ||
|
|
d004815a68 | ||
|
|
5d76bd6b9c | ||
|
|
d02947f887 | ||
|
|
a3a95a1496 | ||
|
|
d47c6b3fdb | ||
|
|
ebbb1dcce3 | ||
|
|
8fbb1ed426 | ||
|
|
d8adc8eeb1 | ||
|
|
a7bd4aee85 | ||
|
|
82fcda4b42 | ||
|
|
b3b7a09cfb | ||
|
|
60ae817f30 | ||
|
|
74166fc2cb | ||
|
|
fb997045f2 | ||
|
|
73a79cf29b | ||
|
|
54a1663a8b | ||
|
|
883d745784 | ||
|
|
571e93a19a | ||
|
|
d16428da6c | ||
|
|
8c5438663c | ||
|
|
ed76d163cf | ||
|
|
9215725dad | ||
|
|
00131668aa | ||
|
|
0306ad737f | ||
|
|
20dfc34e70 | ||
|
|
b7e8b252d2 | ||
|
|
74f09c332b | ||
|
|
3ce6c23603 | ||
|
|
ad3ee14501 | ||
|
|
1931758fc7 | ||
|
|
fe551af145 | ||
|
|
9fdaaed100 | ||
|
|
c0f8a355e3 | ||
|
|
df95ec4ce8 | ||
|
|
a15cad4d03 | ||
|
|
8904f20eb8 | ||
|
|
0ee81d0739 | ||
|
|
c0bc6f8ccb | ||
|
|
1f1cd1e236 | ||
|
|
dbbd78fb26 | ||
|
|
f6e45842fd | ||
|
|
d25c043046 | ||
|
|
5695ba7c13 | ||
|
|
eeb0322ce1 | ||
|
|
480977c2b5 | ||
|
|
8b2e46a6fa | ||
|
|
842be660b0 | ||
|
|
4f69c0e502 | ||
|
|
be128181e2 | ||
|
|
0aa1628925 | ||
|
|
fd58f669d0 | ||
|
|
51927f82c0 | ||
|
|
0f8a0eaf4a | ||
|
|
b9ac22d4ed | ||
|
|
ebca5c6409 | ||
|
|
6ef35e4dba | ||
|
|
8bb5200dd8 | ||
|
|
4aeb6b7c9b | ||
|
|
ae64d7ba82 | ||
|
|
9dd2dbff19 | ||
|
|
cebbffe230 | ||
|
|
e3e464abfa | ||
|
|
268438b0ff | ||
|
|
355c8de5e7 | ||
|
|
e6056bc4a4 | ||
|
|
f2eebe95bd | ||
|
|
3084013269 | ||
|
|
68568e5a25 | ||
|
|
0eae0bb8fe | ||
|
|
b61820d354 | ||
|
|
1606272850 | ||
|
|
ea04fe1c9b | ||
|
|
4c50b6a8af | ||
|
|
39aa848e9e | ||
|
|
64c56c1b17 | ||
|
|
8e883a6b10 | ||
|
|
9684aa3ad5 | ||
|
|
5fede84bf6 | ||
|
|
a788e07be1 | ||
|
|
68ffd2248b | ||
|
|
4b12d021e4 | ||
|
|
4daf9db9db | ||
|
|
a660b1eb05 | ||
|
|
6f2f098e12 | ||
|
|
6a75ef86b0 | ||
|
|
3d2397879c | ||
|
|
de474089a7 | ||
|
|
8b31b9aa05 | ||
|
|
ad41672958 | ||
|
|
86f2984b30 | ||
|
|
38e62786e4 | ||
|
|
ac3d7c9fae | ||
|
|
98f94ea694 | ||
|
|
8ad80f7ac3 | ||
|
|
296678046c | ||
|
|
fd193e651d | ||
|
|
becad5ee67 | ||
|
|
161dcbdd4c | ||
|
|
7ad6652cb0 | ||
|
|
c467167e91 | ||
|
|
18093824d8 | ||
|
|
8102f3c33c | ||
|
|
902c441248 | ||
|
|
e641bb3d57 | ||
|
|
19d938ed60 | ||
|
|
5561949f9b | ||
|
|
5e98cca062 | ||
|
|
ac858c9bee | ||
|
|
f58f5e4e02 | ||
|
|
ad844f5e51 | ||
|
|
53c1bc45d6 | ||
|
|
0cc93aa87e | ||
|
|
b41b830086 | ||
|
|
c79d0890c0 | ||
|
|
5071e6566f | ||
|
|
ac77fe360d | ||
|
|
711aceba0e | ||
|
|
f010279303 | ||
|
|
0c67f4940a | ||
|
|
7836aeacc6 | ||
|
|
af0cc5c899 | ||
|
|
aa2808d621 | ||
|
|
621067acd8 | ||
|
|
3fcba33b1b | ||
|
|
9a66dae0ba | ||
|
|
e2ef8d2dc8 | ||
|
|
eb089bb207 | ||
|
|
4565c3311c | ||
|
|
e30b41a85a | ||
|
|
7b0321043d | ||
|
|
c4b7062d65 | ||
|
|
eb1c1b9475 | ||
|
|
67725f0cba | ||
|
|
68c4ad2bf9 | ||
|
|
dbafda9822 | ||
|
|
6d5d2a1c4d | ||
|
|
b748b8d077 | ||
|
|
f86c448359 | ||
|
|
d0af4b8ddd | ||
|
|
39103c07c9 | ||
|
|
8382ebc057 | ||
|
|
880ba8f1dd | ||
|
|
f00849b8d1 | ||
|
|
c090f78898 | ||
|
|
46a1713122 | ||
|
|
4a88b33782 | ||
|
|
a1a2a141e4 | ||
|
|
97404455d4 | ||
|
|
be31100bd7 | ||
|
|
4335c9ab92 | ||
|
|
20df7156cf | ||
|
|
9c35425e57 | ||
|
|
65df5ac0e4 | ||
|
|
84fdd03603 | ||
|
|
e6ec8bdf4d | ||
|
|
86c8197104 | ||
|
|
bb13badcf4 | ||
|
|
64b2397fa2 | ||
|
|
90130457e2 | ||
|
|
260521fb28 | ||
|
|
6390d595f5 | ||
|
|
74dc46f8b2 | ||
|
|
67262910cd | ||
|
|
d59e68de6d | ||
|
|
b50318fbca | ||
|
|
1ce7df324d | ||
|
|
e3b7264208 | ||
|
|
5e3f6e6c2c | ||
|
|
5b6c43118e | ||
|
|
beee545568 | ||
|
|
a597defd03 | ||
|
|
e3562cdfb5 | ||
|
|
86a7824ec9 | ||
|
|
7001515a44 | ||
|
|
36044c4ab3 | ||
|
|
4933295810 | ||
|
|
ed0cd30092 | ||
|
|
1132d9d0dc | ||
|
|
9bd3a362a2 | ||
|
|
844076badb | ||
|
|
c0523a31a7 | ||
|
|
1fff642860 | ||
|
|
89fe383c9f | ||
|
|
4a173c48b8 | ||
|
|
a72a3e45e9 | ||
|
|
a8d8c992a4 | ||
|
|
99f9a121cb | ||
|
|
6a0e2392fe | ||
|
|
addec46feb | ||
|
|
a4fe9a51b5 | ||
|
|
78b8bbb5aa | ||
|
|
d7ece4f583 | ||
|
|
8fc9b9ae9d | ||
|
|
d1175bf7de | ||
|
|
3900453482 | ||
|
|
68e7e76b31 | ||
|
|
986d196a37 | ||
|
|
d30fb213d2 | ||
|
|
3f02de8214 | ||
|
|
7421cea0da | ||
|
|
507daa6a0c | ||
|
|
61f68af435 | ||
|
|
b3f4cfab32 | ||
|
|
c3b1de498d | ||
|
|
cdf059361e | ||
|
|
2aada9a7da | ||
|
|
b96786125c | ||
|
|
9451f38cec | ||
|
|
306f53dec7 | ||
|
|
db865a680f | ||
|
|
aca552c9f7 | ||
|
|
b202c4e79f | ||
|
|
972ce6911a | ||
|
|
751530b460 | ||
|
|
3b263e9282 | ||
|
|
2cbf9e1ac8 | ||
|
|
6283535d6a | ||
|
|
a03ae81509 | ||
|
|
a7e05aa016 | ||
|
|
f8cbf8b1c4 | ||
|
|
ccc294e567 | ||
|
|
0be344b1ba | ||
|
|
7a525e476a | ||
|
|
fe62f9c6de | ||
|
|
9608f6518f | ||
|
|
2f8fa20ec6 | ||
|
|
6f097b0a74 | ||
|
|
159bbb53b6 | ||
|
|
622160ea17 | ||
|
|
5d543c864f | ||
|
|
ed745623eb | ||
|
|
29bca76e91 | ||
|
|
33d0fee43e | ||
|
|
bb13de4176 | ||
|
|
c854dd092c | ||
|
|
26d64bd93f | ||
|
|
1025238eee | ||
|
|
eec76ecfe2 | ||
|
|
f5876ae6d6 | ||
|
|
ccc8cdc1ff | ||
|
|
a1840c9362 | ||
|
|
22212e7edb | ||
|
|
6ffe7005dd | ||
|
|
4890d72e30 | ||
|
|
a531541273 | ||
|
|
2bced59b47 | ||
|
|
e9554604e7 | ||
|
|
48c51da853 | ||
|
|
cc2164657c | ||
|
|
a35683667b | ||
|
|
cf403bebf1 | ||
|
|
f3e4bbc753 | ||
|
|
4bfbac6649 | ||
|
|
3d7fd7eb01 | ||
|
|
ec42900e25 | ||
|
|
032b243248 | ||
|
|
b70b5a5449 | ||
|
|
cc5dfb06bd |
No files matched your search
@@ -38,6 +38,7 @@ BASECAMP3_USER_AGENT=
|
|||||||
DAILY_API_KEY=
|
DAILY_API_KEY=
|
||||||
DAILY_SCALE_PLAN=''
|
DAILY_SCALE_PLAN=''
|
||||||
DAILY_WEBHOOK_SECRET=''
|
DAILY_WEBHOOK_SECRET=''
|
||||||
|
DAILY_MEETING_ENDED_WEBHOOK_SECRET=''
|
||||||
|
|
||||||
# - GOOGLE CALENDAR/MEET/LOGIN
|
# - GOOGLE CALENDAR/MEET/LOGIN
|
||||||
# Needed to enable Google Calendar integration and Login with Google
|
# Needed to enable Google Calendar integration and Login with Google
|
||||||
|
|||||||
+16
-3
@@ -169,8 +169,7 @@ STRIPE_WEBHOOK_SECRET=
|
|||||||
STRIPE_WEBHOOK_SECRET_APPS=
|
STRIPE_WEBHOOK_SECRET_APPS=
|
||||||
STRIPE_PRIVATE_KEY=
|
STRIPE_PRIVATE_KEY=
|
||||||
STRIPE_CLIENT_ID=
|
STRIPE_CLIENT_ID=
|
||||||
PAYMENT_FEE_FIXED=
|
|
||||||
PAYMENT_FEE_PERCENTAGE=
|
|
||||||
|
|
||||||
# Use for internal Public API Keys and optional
|
# Use for internal Public API Keys and optional
|
||||||
API_KEY_PREFIX=cal_
|
API_KEY_PREFIX=cal_
|
||||||
@@ -213,6 +212,10 @@ E2E_TEST_MAILHOG_ENABLED=
|
|||||||
|
|
||||||
# **********************************************************************************************************
|
# **********************************************************************************************************
|
||||||
|
|
||||||
|
# Cloudflare Turnstile
|
||||||
|
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
|
||||||
|
CLOUDFLARE_TURNSTILE_SECRET=
|
||||||
|
|
||||||
# Set the following value to true if you wish to enable Team Impersonation
|
# Set the following value to true if you wish to enable Team Impersonation
|
||||||
NEXT_PUBLIC_TEAM_IMPERSONATION=false
|
NEXT_PUBLIC_TEAM_IMPERSONATION=false
|
||||||
|
|
||||||
@@ -237,7 +240,7 @@ CSP_POLICY=
|
|||||||
# Vercel Edge Config
|
# Vercel Edge Config
|
||||||
EDGE_CONFIG=
|
EDGE_CONFIG=
|
||||||
|
|
||||||
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
|
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
|
||||||
NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD=0 # Override the booker to only load X number of days worth of data
|
NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD=0 # Override the booker to only load X number of days worth of data
|
||||||
|
|
||||||
# Control time intervals on a user's Schedule availability
|
# Control time intervals on a user's Schedule availability
|
||||||
@@ -296,6 +299,7 @@ E2E_TEST_CALCOM_GCAL_KEYS=
|
|||||||
CALCOM_CREDENTIAL_SYNC_SECRET=""
|
CALCOM_CREDENTIAL_SYNC_SECRET=""
|
||||||
# This is the header name that will be used to verify the webhook secret. Should be in lowercase
|
# This is the header name that will be used to verify the webhook secret. Should be in lowercase
|
||||||
CALCOM_CREDENTIAL_SYNC_HEADER_NAME="calcom-credential-sync-secret"
|
CALCOM_CREDENTIAL_SYNC_HEADER_NAME="calcom-credential-sync-secret"
|
||||||
|
# This the endpoint from which the token is fetched
|
||||||
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
|
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
|
||||||
# Key should match on Cal.com and your application
|
# Key should match on Cal.com and your application
|
||||||
# must be 24 bytes for AES256 encryption algorithm
|
# must be 24 bytes for AES256 encryption algorithm
|
||||||
@@ -344,5 +348,14 @@ SENTRY_DISABLE_SERVER_WEBPACK_PLUGIN=1
|
|||||||
# api v2
|
# api v2
|
||||||
NEXT_PUBLIC_API_V2_URL="http://localhost:5555/api/v2"
|
NEXT_PUBLIC_API_V2_URL="http://localhost:5555/api/v2"
|
||||||
|
|
||||||
|
# Tasker features
|
||||||
|
TASKER_ENABLE_WEBHOOKS=0
|
||||||
|
TASKER_ENABLE_EMAILS=0
|
||||||
|
|
||||||
# Ratelimiting via unkey
|
# Ratelimiting via unkey
|
||||||
UNKEY_ROOT_KEY=
|
UNKEY_ROOT_KEY=
|
||||||
|
|
||||||
|
|
||||||
|
# Used for Cal.ai Enterprise Voice AI Agents
|
||||||
|
# https://retellai.com
|
||||||
|
RETELL_AI_KEY=
|
||||||
@@ -4,3 +4,4 @@ node_modules
|
|||||||
**/**/public
|
**/**/public
|
||||||
packages/prisma/zod
|
packages/prisma/zod
|
||||||
apps/web/public/embed
|
apps/web/public/embed
|
||||||
|
packages/ui/components/icon/dynamicIconImports.tsx
|
||||||
@@ -2,29 +2,18 @@
|
|||||||
|
|
||||||
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
|
<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->
|
||||||
|
|
||||||
Fixes # (issue)
|
- Fixes #XXXX (GitHub issue number)
|
||||||
|
- Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)
|
||||||
|
|
||||||
<!-- Please provide a loom video for visual changes to speed up reviews
|
<!-- Please provide a loom video for visual changes to speed up reviews
|
||||||
Loom Video: https://www.loom.com/
|
Loom Video: https://www.loom.com/
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Requirement/Documentation
|
## Mandatory Tasks (DO NOT REMOVE)
|
||||||
|
|
||||||
<!-- Please provide all documents that are important to understand the reason of that PR. -->
|
- [ ] I have self-reviewed the code (A decent size PR without self-review might be rejected)
|
||||||
|
- [ ] I have added a Docs issue [here](https://github.com/calcom/docs/issues/new) if this PR makes changes that would require a [documentation change](https://docs.cal.com)
|
||||||
- If there is a requirement document, please, share it here.
|
- [ ] I have added or modified automated tests that prove my fix is effective or that my feature works (PRs might be rejected if logical changes are not properly tested)
|
||||||
- If there is a UI/UX design document, please, share it here.
|
|
||||||
|
|
||||||
## Type of change
|
|
||||||
|
|
||||||
<!-- Please delete bullets that are not relevant. -->
|
|
||||||
|
|
||||||
- Bug fix (non-breaking change which fixes an issue)
|
|
||||||
- Chore (refactoring code, technical debt, workflow improvements)
|
|
||||||
- New feature (non-breaking change which adds functionality)
|
|
||||||
- Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
|
||||||
- Tests (Unit/Integration/E2E or any other test)
|
|
||||||
- This change requires a documentation update
|
|
||||||
|
|
||||||
## How should this be tested?
|
## How should this be tested?
|
||||||
|
|
||||||
@@ -35,10 +24,6 @@ Fixes # (issue)
|
|||||||
- What is expected (happy path) to have (input and output)?
|
- What is expected (happy path) to have (input and output)?
|
||||||
- Any other important info that could help to test that PR
|
- Any other important info that could help to test that PR
|
||||||
|
|
||||||
## Mandatory Tasks
|
|
||||||
|
|
||||||
- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
|
|
||||||
|
|
||||||
## Checklist
|
## Checklist
|
||||||
|
|
||||||
<!-- Remove bullet points below that don't apply to you -->
|
<!-- Remove bullet points below that don't apply to you -->
|
||||||
@@ -46,7 +31,4 @@ Fixes # (issue)
|
|||||||
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
|
- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
|
||||||
- My code doesn't follow the style guidelines of this project
|
- My code doesn't follow the style guidelines of this project
|
||||||
- I haven't commented my code, particularly in hard-to-understand areas
|
- I haven't commented my code, particularly in hard-to-understand areas
|
||||||
- I haven't checked if my PR needs changes to the documentation
|
|
||||||
- I haven't checked if my changes generate no new warnings
|
- I haven't checked if my changes generate no new warnings
|
||||||
- I haven't added tests that prove my fix is effective or that my feature works
|
|
||||||
- I haven't checked if new and existing unit tests pass locally with my changes
|
|
||||||
@@ -8,14 +8,17 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Cache production build
|
- name: Cache production build
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
id: cache-build
|
id: cache-build
|
||||||
env:
|
env:
|
||||||
|
# WARN: Don't touch this cache key. Currently github.sha refers to the latest commit in main
|
||||||
|
# and not the branch that's attempting to merge to main, which causes CI errors when merged
|
||||||
|
# to main.
|
||||||
|
# TODO: Fix this problem if intending to modify this cache key.
|
||||||
cache-name: prod-build
|
cache-name: prod-build
|
||||||
key-1: ${{ inputs.node_version }}-${{ hashFiles('yarn.lock') }}
|
key-1: ${{ inputs.node_version }}-${{ hashFiles('yarn.lock') }}
|
||||||
key-2: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', '!**/node_modules') }}
|
key-2: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', '!**/node_modules') }}
|
||||||
key-3: ${{ github.event.pull_request.number || github.ref }}
|
key-3: ${{ github.event.pull_request.number || github.ref }}
|
||||||
# Ensures production-build.yml will always be fresh
|
|
||||||
key-4: ${{ github.sha }}
|
key-4: ${{ github.sha }}
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ description: "Cache or restore if necessary"
|
|||||||
inputs:
|
inputs:
|
||||||
DATABASE_URL:
|
DATABASE_URL:
|
||||||
required: false
|
required: false
|
||||||
default: "postgresql://postgres:@localhost:5432/calendso"
|
default: "postgresql://postgres:postgres@localhost:5432/calendso"
|
||||||
path:
|
path:
|
||||||
required: false
|
required: false
|
||||||
default: "backups/backup.sql"
|
default: "backups/backup.sql"
|
||||||
@@ -12,7 +12,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Cache database
|
- name: Cache database
|
||||||
id: cache-db
|
id: cache-db
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
env:
|
env:
|
||||||
cache-name: cache-db
|
cache-name: cache-db
|
||||||
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
fetch-depth: 2
|
fetch-depth: 2
|
||||||
@@ -20,7 +20,7 @@ runs:
|
|||||||
using: "composite"
|
using: "composite"
|
||||||
steps:
|
steps:
|
||||||
- name: Use Node ${{ inputs.node_version }}
|
- name: Use Node ${{ inputs.node_version }}
|
||||||
uses: buildjet/setup-node@v3
|
uses: buildjet/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.node_version }}
|
node-version: ${{ inputs.node_version }}
|
||||||
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
- name: Expose yarn config as "$GITHUB_OUTPUT"
|
||||||
@@ -32,7 +32,7 @@ runs:
|
|||||||
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
|
# Yarn rotates the downloaded cache archives, @see https://github.com/actions/setup-node/issues/325
|
||||||
# Yarn cache is also reusable between arch and os.
|
# Yarn cache is also reusable between arch and os.
|
||||||
- name: Restore yarn cache
|
- name: Restore yarn cache
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
id: yarn-download-cache
|
id: yarn-download-cache
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
|
||||||
@@ -43,7 +43,7 @@ runs:
|
|||||||
# Invalidated on yarn.lock changes
|
# Invalidated on yarn.lock changes
|
||||||
- name: Restore node_modules
|
- name: Restore node_modules
|
||||||
id: yarn-nm-cache
|
id: yarn-nm-cache
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
with:
|
with:
|
||||||
path: "**/node_modules/"
|
path: "**/node_modules/"
|
||||||
key: ${{ runner.os }}-yarn-nm-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
key: ${{ runner.os }}-yarn-nm-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||||
@@ -51,7 +51,7 @@ runs:
|
|||||||
# Invalidated on yarn.lock changes
|
# Invalidated on yarn.lock changes
|
||||||
- name: Restore yarn install state
|
- name: Restore yarn install state
|
||||||
id: yarn-install-state-cache
|
id: yarn-install-state-cache
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .yarn/ci-cache/
|
path: .yarn/ci-cache/
|
||||||
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ runs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
uses: buildjet/cache@v3
|
uses: buildjet/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/Library/Caches/ms-playwright
|
~/Library/Caches/ms-playwright
|
||||||
|
|||||||
+7
-3
@@ -1,6 +1,10 @@
|
|||||||
"❗️ migrations":
|
"❗️ migrations":
|
||||||
- packages/prisma/migrations/**/migration.sql
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- packages/prisma/migrations/**/migration.sql
|
||||||
|
|
||||||
"❗️ .env changes":
|
"❗️ .env changes":
|
||||||
- .env.example
|
- changed-files:
|
||||||
- .env.appStore.example
|
- any-glob-to-any-file:
|
||||||
|
- .env.example
|
||||||
|
- .env.appStore.example
|
||||||
+37
-6
@@ -1,4 +1,4 @@
|
|||||||
name: Production Build
|
name: Production Builds
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
@@ -18,7 +18,10 @@ env:
|
|||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
@@ -33,27 +36,55 @@ env:
|
|||||||
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Build API v1
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13.13
|
image: postgres:13
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
- uses: ./.github/actions/cache-build
|
- name: Cache API v1 production build
|
||||||
|
uses: buildjet/cache@v3
|
||||||
|
id: cache-api-v1-build
|
||||||
|
env:
|
||||||
|
cache-name: api-v1-build
|
||||||
|
key-1: ${{ hashFiles('yarn.lock') }}
|
||||||
|
key-2: ${{ hashFiles('apps/api/v1/**.[jt]s', 'apps/api/v1/**.[jt]sx', '!**/node_modules') }}
|
||||||
|
key-3: ${{ github.event.pull_request.number || github.ref }}
|
||||||
|
# Ensures production-build.yml will always be fresh
|
||||||
|
key-4: ${{ github.sha }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
${{ github.workspace }}/apps/api/v1/.next
|
||||||
|
**/.turbo/**
|
||||||
|
**/dist/**
|
||||||
|
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}
|
||||||
|
- run: |
|
||||||
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
|
if [ ${{ steps.cache-api-v1-build.outputs.cache-hit }} == 'true' ]; then
|
||||||
|
echo "Cache hit for API v1 build. Skipping build."
|
||||||
|
else
|
||||||
|
yarn turbo run build --filter=@calcom/api...
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
name: Production Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build API v2
|
||||||
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
timeout-minutes: 30
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
- uses: ./.github/actions/yarn-install
|
||||||
|
- name: Cache API v2 production build
|
||||||
|
uses: buildjet/cache@v3
|
||||||
|
id: cache-api-v2-build
|
||||||
|
env:
|
||||||
|
cache-name: api-v2-build
|
||||||
|
key-1: ${{ hashFiles('yarn.lock') }}
|
||||||
|
key-2: ${{ hashFiles('apps/api/v2/**.[jt]s', 'apps/api/v2/**.[jt]sx', '!**/node_modules') }}
|
||||||
|
key-3: ${{ github.event.pull_request.number || github.ref }}
|
||||||
|
# Ensures production-build.yml will always be fresh
|
||||||
|
key-4: ${{ github.sha }}
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
**/dist/**
|
||||||
|
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}
|
||||||
|
- run: |
|
||||||
|
export NODE_OPTIONS="--max_old_space_size=8192"
|
||||||
|
if [ ${{ steps.cache-api-v2-build.outputs.cache-hit }} == 'true' ]; then
|
||||||
|
echo "Cache hit for API v2 build. Skipping build."
|
||||||
|
else
|
||||||
|
yarn workspace @calcom/api-v2 run generate-schemas
|
||||||
|
rm -rf apps/api/v2/node_modules
|
||||||
|
yarn install
|
||||||
|
yarn workspace @calcom/api-v2 run build
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
@@ -6,10 +6,10 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Cleanup
|
- name: Cleanup
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
|
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ jobs:
|
|||||||
check-types:
|
check-types:
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- name: Show info
|
- name: Show info
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Cron - changeTimeZone
|
||||||
|
|
||||||
|
on:
|
||||||
|
# "Scheduled workflows run on the latest commit on the default or base branch."
|
||||||
|
# — https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule
|
||||||
|
schedule:
|
||||||
|
# Runs "At every full hour." (see https://crontab.guru)
|
||||||
|
- cron: "0 * * * *"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cron-scheduleEmailReminders:
|
||||||
|
env:
|
||||||
|
APP_URL: ${{ secrets.APP_URL }}
|
||||||
|
CRON_API_KEY: ${{ secrets.CRON_API_KEY }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: cURL request
|
||||||
|
if: ${{ env.APP_URL && env.CRON_API_KEY }}
|
||||||
|
run: |
|
||||||
|
curl ${{ secrets.APP_URL }}/api/cron/changeTimeZone \
|
||||||
|
-X POST \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-H 'authorization: ${{ secrets.CRON_API_KEY }}' \
|
||||||
|
-sSf
|
||||||
@@ -18,9 +18,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
LAST_DAY=$(date -d tomorrow +%d)
|
LAST_DAY=$(date -d tomorrow +%d)
|
||||||
if [ "$LAST_DAY" == "01" ]; then
|
if [ "$LAST_DAY" == "01" ]; then
|
||||||
echo "::set-output name=is_last_day::true"
|
echo "is_last_day=true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "::set-output name=is_last_day::false"
|
echo "is_last_day=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: cURL request
|
- name: cURL request
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: buildjet-2vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: buildjet/cache-delete@v1
|
- uses: buildjet/cache-delete@v1
|
||||||
with:
|
with:
|
||||||
cache_key: ${{ inputs.cache_key }}
|
cache_key: ${{ inputs.cache_key }}
|
||||||
@@ -1,8 +1,43 @@
|
|||||||
name: E2E App-Store Apps Tests
|
name: E2E App Store Tests
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
jobs:
|
jobs:
|
||||||
e2e-app-store:
|
e2e-app-store:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -10,13 +45,19 @@ jobs:
|
|||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13.13
|
image: postgres:13
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
@@ -38,7 +79,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
@@ -53,38 +94,6 @@ jobs:
|
|||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn e2e:app-store --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
run: yarn e2e:app-store --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||||
env:
|
|
||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
|
||||||
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
|
||||||
E2E_TEST_CALCOM_GCAL_KEYS: ${{ secrets.E2E_TEST_CALCOM_GCAL_KEYS }}
|
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
|
||||||
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
|
||||||
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
|
||||||
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
|
||||||
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
|
||||||
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
|
||||||
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
|
||||||
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
|
||||||
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
|
||||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
|
||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
|
||||||
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
|
||||||
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -3,6 +3,41 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
jobs:
|
jobs:
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -10,13 +45,19 @@ jobs:
|
|||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13.13
|
image: postgres:13
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
strategy:
|
strategy:
|
||||||
@@ -30,7 +71,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
@@ -40,33 +81,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
yarn e2e:embed-react --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
yarn e2e:embed-react --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||||
yarn workspace @calcom/embed-react packaged:tests
|
yarn workspace @calcom/embed-react packaged:tests
|
||||||
env:
|
|
||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
|
||||||
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
|
||||||
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
|
||||||
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
|
||||||
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
|
||||||
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
|
||||||
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
|
||||||
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
|
||||||
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
|
||||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
|
||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
|
||||||
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
|
||||||
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -3,6 +3,41 @@ on:
|
|||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
jobs:
|
jobs:
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
@@ -10,13 +45,19 @@ jobs:
|
|||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13.13
|
image: postgres:13
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
@@ -38,7 +79,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
@@ -46,34 +87,6 @@ jobs:
|
|||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn e2e:embed --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
run: yarn e2e:embed --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||||
env:
|
|
||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
|
||||||
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
|
||||||
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
|
||||||
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
|
||||||
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
|
||||||
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
|
||||||
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
|
||||||
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
|
||||||
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
|
||||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
|
||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
|
||||||
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
|
||||||
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
+46
-37
@@ -1,22 +1,63 @@
|
|||||||
name: E2E tests
|
name: E2E
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: --max-old-space-size=4096
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
jobs:
|
jobs:
|
||||||
e2e:
|
e2e:
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
name: E2E tests (${{ matrix.shard }}/${{ strategy.job-total }})
|
name: E2E (${{ matrix.shard }}/${{ strategy.job-total }})
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:13.13
|
image: postgres:13
|
||||||
credentials:
|
credentials:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
@@ -30,13 +71,13 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5]
|
shard: [1, 2, 3, 4, 5, 6, 7]
|
||||||
steps:
|
steps:
|
||||||
- uses: docker/login-action@v3
|
- uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/yarn-playwright-install
|
- uses: ./.github/actions/yarn-playwright-install
|
||||||
@@ -44,38 +85,6 @@ jobs:
|
|||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: yarn e2e --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
run: yarn e2e --shard=${{ matrix.shard }}/${{ strategy.job-total }}
|
||||||
env:
|
|
||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
|
||||||
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
|
||||||
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
|
||||||
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
|
||||||
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
|
||||||
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
|
||||||
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
|
||||||
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
|
||||||
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
|
||||||
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
|
||||||
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
|
||||||
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
|
||||||
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
|
||||||
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
|
||||||
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
|
||||||
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
|
||||||
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
|
||||||
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
|
||||||
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
|
||||||
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
|
||||||
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
name: Integration
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
env:
|
||||||
|
NODE_OPTIONS: --max-old-space-size=4096
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
|
jobs:
|
||||||
|
integration:
|
||||||
|
timeout-minutes: 20
|
||||||
|
name: Integration
|
||||||
|
runs-on: buildjet-8vcpu-ubuntu-2204
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:13
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: calendso
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
mailhog:
|
||||||
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
ports:
|
||||||
|
- 8025:8025
|
||||||
|
- 1025:1025
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
|
- uses: ./.github/actions/yarn-install
|
||||||
|
- uses: ./.github/actions/cache-db
|
||||||
|
- name: Run Tests
|
||||||
|
run: yarn test -- --integrationTestsOnly
|
||||||
|
- name: Upload Test Results
|
||||||
|
if: ${{ always() }}
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: test-results
|
||||||
@@ -12,18 +12,16 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/labeler@v4
|
- uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
|
||||||
sync-labels: ""
|
|
||||||
team-labels:
|
team-labels:
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- uses: equitybee/team-label-action@main
|
- uses: equitybee/team-label-action@main
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||||
@@ -39,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Apply labels from linked issue to PR
|
- name: Apply labels from linked issue to PR
|
||||||
uses: actions/github-script@v5
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
script: |
|
script: |
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ jobs:
|
|||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- name: Run Linting with Reports
|
- name: Run Linting with Reports
|
||||||
|
|||||||
@@ -7,18 +7,55 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
|
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
|
||||||
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
E2E_TEST_MAILHOG_ENABLED: ${{ vars.E2E_TEST_MAILHOG_ENABLED }}
|
||||||
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
EMAIL_SERVER_HOST: ${{ secrets.CI_EMAIL_SERVER_HOST }}
|
||||||
|
EMAIL_SERVER_PORT: ${{ secrets.CI_EMAIL_SERVER_PORT }}
|
||||||
|
EMAIL_SERVER_USER: ${{ secrets.CI_EMAIL_SERVER_USER }}
|
||||||
|
EMAIL_SERVER_PASSWORD: ${{ secrets.CI_EMAIL_SERVER_PASSWORD}}
|
||||||
|
GOOGLE_LOGIN_ENABLED: ${{ vars.CI_GOOGLE_LOGIN_ENABLED }}
|
||||||
|
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
|
||||||
|
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_ROOT_URL }}
|
||||||
|
NEXT_PUBLIC_IS_E2E: ${{ vars.CI_NEXT_PUBLIC_IS_E2E }}
|
||||||
|
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY: ${{ secrets.CI_NEXT_PUBLIC_STRIPE_PUBLIC_KEY }}
|
||||||
|
NEXT_PUBLIC_WEBAPP_URL: ${{ vars.CI_NEXT_PUBLIC_WEBAPP_URL }}
|
||||||
|
NEXT_PUBLIC_WEBSITE_URL: ${{ vars.CI_NEXT_PUBLIC_WEBSITE_URL }}
|
||||||
|
PAYMENT_FEE_FIXED: ${{ vars.CI_PAYMENT_FEE_FIXED }}
|
||||||
|
PAYMENT_FEE_PERCENTAGE: ${{ vars.CI_PAYMENT_FEE_PERCENTAGE }}
|
||||||
|
SAML_ADMINS: ${{ secrets.CI_SAML_ADMINS }}
|
||||||
|
SAML_DATABASE_URL: ${{ secrets.CI_SAML_DATABASE_URL }}
|
||||||
|
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
|
||||||
|
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
|
||||||
|
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
|
||||||
|
SENDGRID_API_KEY: ${{ secrets.CI_SENDGRID_API_KEY }}
|
||||||
|
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
|
||||||
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Production build
|
name: Production builds
|
||||||
if: ${{ github.event_name == 'push' }}
|
if: ${{ github.event_name == 'push' }}
|
||||||
uses: ./.github/workflows/production-build.yml
|
uses: ./.github/workflows/production-build-without-database.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
analyze:
|
analyze:
|
||||||
needs: build
|
needs: build
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
|
|||||||
+34
-20
@@ -14,15 +14,15 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
changes:
|
changes:
|
||||||
name: Detect changes
|
name: Detect changes
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
outputs:
|
outputs:
|
||||||
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
|
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: dorny/paths-filter@v2
|
- uses: dorny/paths-filter@v3
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -35,8 +35,8 @@ jobs:
|
|||||||
uses: ./.github/workflows/check-types.yml
|
uses: ./.github/workflows/check-types.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
test:
|
unit-test:
|
||||||
name: Unit tests
|
name: Tests
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/unit-tests.yml
|
uses: ./.github/workflows/unit-tests.yml
|
||||||
@@ -49,44 +49,58 @@ jobs:
|
|||||||
uses: ./.github/workflows/lint.yml
|
uses: ./.github/workflows/lint.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build:
|
build-api-v1:
|
||||||
name: Production build
|
name: Production builds
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/production-build.yml
|
uses: ./.github/workflows/api-v1-production-build.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-without-database:
|
build-api-v2:
|
||||||
name: Production build (without database)
|
name: Production builds
|
||||||
|
needs: [changes]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
|
uses: ./.github/workflows/api-v2-production-build.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Production builds
|
||||||
needs: [changes]
|
needs: [changes]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/production-build-without-database.yml
|
uses: ./.github/workflows/production-build-without-database.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
|
integration-test:
|
||||||
|
name: Tests
|
||||||
|
needs: [changes, lint, build, build-api-v1, build-api-v2]
|
||||||
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
|
uses: ./.github/workflows/integration-tests.yml
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
name: E2E tests
|
name: Tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build, build-api-v1, build-api-v2]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e.yml
|
uses: ./.github/workflows/e2e.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-app-store:
|
e2e-app-store:
|
||||||
name: E2E App Store tests
|
name: Tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build, build-api-v1, build-api-v2]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-app-store.yml
|
uses: ./.github/workflows/e2e-app-store.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-embed:
|
e2e-embed:
|
||||||
name: E2E embeds tests
|
name: Tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build, build-api-v1, build-api-v2]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-embed.yml
|
uses: ./.github/workflows/e2e-embed.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e-embed-react:
|
e2e-embed-react:
|
||||||
name: E2E React embeds tests
|
name: Tests
|
||||||
needs: [changes, lint, build]
|
needs: [changes, lint, build, build-api-v1, build-api-v2]
|
||||||
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
|
||||||
uses: ./.github/workflows/e2e-embed-react.yml
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
@@ -99,9 +113,9 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
required:
|
required:
|
||||||
needs: [changes, lint, type-check, test, build, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
needs: [changes, lint, type-check, unit-test, integration-test, build, build-api-v1, build-api-v2, e2e, e2e-embed, e2e-embed-react, e2e-app-store]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: fail if conditional jobs failed
|
- name: fail if conditional jobs failed
|
||||||
if: needs.changes.outputs.has-files-requiring-all-checks == 'true' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled'))
|
if: needs.changes.outputs.has-files-requiring-all-checks == 'true' && (contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled'))
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ jobs:
|
|||||||
embed: ${{ steps.filter.outputs.embed }}
|
embed: ${{ steps.filter.outputs.embed }}
|
||||||
embed-react: ${{ steps.filter.outputs.embed-react }}
|
embed-react: ${{ steps.filter.outputs.embed-react }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: dorny/paths-filter@v2
|
- uses: dorny/paths-filter@v3
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -39,8 +39,8 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Production build
|
name: Production builds
|
||||||
uses: ./.github/workflows/production-build.yml
|
uses: ./.github/workflows/production-build-without-database.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
@@ -67,15 +67,10 @@ jobs:
|
|||||||
uses: ./.github/workflows/e2e-embed-react.yml
|
uses: ./.github/workflows/e2e-embed-react.yml
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
build-without-database:
|
|
||||||
name: Production build (without database)
|
|
||||||
uses: ./.github/workflows/production-build-without-database.yml
|
|
||||||
secrets: inherit
|
|
||||||
|
|
||||||
required:
|
required:
|
||||||
needs: [e2e, e2e-app-store, e2e-embed, e2e-embed-react, build-without-database]
|
needs: [changes, lint, build, e2e, e2e-app-store, e2e-embed, e2e-embed-react]
|
||||||
if: always()
|
if: always()
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- name: fail if conditional jobs failed
|
- name: fail if conditional jobs failed
|
||||||
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
name: Production Build (without database)
|
name: Production Builds
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_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 }}
|
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 }}
|
||||||
@@ -30,14 +30,17 @@ env:
|
|||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||||
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_URL }}
|
NEXT_PUBLIC_API_V2_URL: ${{ secrets.CI_NEXT_PUBLIC_API_V2_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 }}
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Web App
|
||||||
runs-on: ubuntu-latest
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
- uses: ./.github/actions/cache-build
|
- uses: ./.github/actions/cache-build
|
||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- 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'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main # Always checkout main even for tagged releases
|
ref: main # Always checkout main even for tagged releases
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
token: ${{ secrets.GH_ACCESS_TOKEN }}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Unit tests
|
name: Unit
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_run:
|
workflow_run:
|
||||||
@@ -6,10 +6,11 @@ on:
|
|||||||
types: [completed]
|
types: [completed]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
name: Unit
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-2vcpu-ubuntu-2204
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
|
# Should be an 8GB machine as per https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ jobs:
|
|||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
@@ -17,3 +17,4 @@ packages/prisma/zod
|
|||||||
packages/prisma/enums
|
packages/prisma/enums
|
||||||
apps/web/public/embed
|
apps/web/public/embed
|
||||||
apps/api/v2/swagger/documentation.json
|
apps/api/v2/swagger/documentation.json
|
||||||
|
packages/ui/components/icon/dynamicIconImports.tsx
|
||||||
+13
-2
@@ -13,8 +13,6 @@ Contributions are what makes the open source community such an amazing place to
|
|||||||
- Issues from non-core members automatically receive the `🚨 needs approval` label.
|
- Issues from non-core members automatically receive the `🚨 needs approval` label.
|
||||||
- We greatly value new feature ideas. To ensure consistency in the product's direction, they undergo review and approval.
|
- We greatly value new feature ideas. To ensure consistency in the product's direction, they undergo review and approval.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Priorities
|
## Priorities
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
@@ -178,11 +176,15 @@ If you get errors, be sure to fix them before committing.
|
|||||||
Do not commit your `yarn.lock` unless you've made changes to the `package.json`. If you've already committed `yarn.lock` unintentionally, follow these steps to undo:
|
Do not commit your `yarn.lock` unless you've made changes to the `package.json`. If you've already committed `yarn.lock` unintentionally, follow these steps to undo:
|
||||||
|
|
||||||
If your last commit has the `yarn.lock` file alongside other files and you only wish to uncommit the `yarn.lock`:
|
If your last commit has the `yarn.lock` file alongside other files and you only wish to uncommit the `yarn.lock`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout HEAD~1 yarn.lock
|
git checkout HEAD~1 yarn.lock
|
||||||
git commit -m "Revert yarn.lock changes"
|
git commit -m "Revert yarn.lock changes"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
_NB_: You may have to bypass the pre-commit hook with by appending `--no-verify` to the git commit
|
||||||
If you've pushed the commit with the `yarn.lock`:
|
If you've pushed the commit with the `yarn.lock`:
|
||||||
|
|
||||||
1. Correct the commit locally using the above method.
|
1. Correct the commit locally using the above method.
|
||||||
2. Carefully force push:
|
2. Carefully force push:
|
||||||
|
|
||||||
@@ -194,26 +196,35 @@ If `yarn.lock` was committed a while ago and there have been several commits sin
|
|||||||
|
|
||||||
1. **Checkout a Previous Version**:
|
1. **Checkout a Previous Version**:
|
||||||
- Find the commit hash before the `yarn.lock` was unintentionally committed. You can do this by viewing the Git log:
|
- Find the commit hash before the `yarn.lock` was unintentionally committed. You can do this by viewing the Git log:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git log yarn.lock
|
git log yarn.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
- Once you have identified the commit hash, use it to checkout the previous version of `yarn.lock`:
|
- Once you have identified the commit hash, use it to checkout the previous version of `yarn.lock`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout <commit_hash> yarn.lock
|
git checkout <commit_hash> yarn.lock
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Commit the Reverted Version**:
|
2. **Commit the Reverted Version**:
|
||||||
- After checking out the previous version of the `yarn.lock`, commit this change:
|
- After checking out the previous version of the `yarn.lock`, commit this change:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git commit -m "Revert yarn.lock to its state before unintended changes"
|
git commit -m "Revert yarn.lock to its state before unintended changes"
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Proceed with Caution**:
|
3. **Proceed with Caution**:
|
||||||
- If you need to push this change, first pull the latest changes from your remote branch to ensure you're not overwriting other recent changes:
|
- If you need to push this change, first pull the latest changes from your remote branch to ensure you're not overwriting other recent changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git pull origin <your-branch-name>
|
git pull origin <your-branch-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
- Then push the updated branch:
|
- Then push the updated branch:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git push origin <your-branch-name>
|
git push origin <your-branch-name>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Lastly, make sure to keep the branches updated (e.g. click the `Update branch` button on GitHub PR).
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://go.cal.com/discord"><img src="https://img.shields.io/badge/Discord-go.cal.com%2Fdiscord-%234A154B" alt="Join Cal.com Discord"></a>
|
<a href="https://go.cal.com/discord"><img src="https://img.shields.io/badge/Discord-go.cal.com%2Fdiscord-%234A154B" alt="Join Cal.com Discord"></a>
|
||||||
<a href="https://producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
|
<a href="https://www.producthunt.com/products/cal-com"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
|
||||||
<a href="https://status.cal.com"><img height="20px" src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
|
<a href="https://status.cal.com"><img height="20px" src="https://betteruptime.com/status-badges/v1/monitor/a9kf.svg" alt="Uptime"></a>
|
||||||
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
|
<a href="https://github.com/calcom/cal.com/stargazers"><img src="https://img.shields.io/github/stars/calcom/cal.com" alt="Github Stars"></a>
|
||||||
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
|
<a href="https://news.ycombinator.com/item?id=34507672"><img src="https://img.shields.io/badge/Hacker%20News-%231-%23FF6600" alt="Hacker News"></a>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-Free-brightgreen" alt="Pricing"></a>
|
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-Free-brightgreen" alt="Pricing"></a>
|
||||||
<a href="https://jitsu.com?utm_source=github/calcom/cal.com"><img src="https://img.shields.io/badge/Metrics_tracked_by-JITSU-AA00FF?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACKSURBVHgBrZDRCYAwDEQv6gCOoKO4hOCXI9QVnEZwiY5iF5GaVClaBNtioCSUvCR3tMJaxIfZgW4AGUoEPVwgPZoS0Dmgg3NBVDFNbMIsmYCak3J1jDk9iCQvsKJvkzr71N81Gj6vDT/LU2P6RhY63jcafk3YJEbgeZpiFyc/5HJKv8Ef273NSfABGbQfUZhnOSAAAAAASUVORK5CYII=" alt="Jitsu Tracked"></a>
|
<a href="https://jitsu.com?utm_source=github/calcom/cal.com"><img src="https://img.shields.io/badge/Metrics_tracked_by-JITSU-AA00FF?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACKSURBVHgBrZDRCYAwDEQv6gCOoKO4hOCXI9QVnEZwiY5iF5GaVClaBNtioCSUvCR3tMJaxIfZgW4AGUoEPVwgPZoS0Dmgg3NBVDFNbMIsmYCak3J1jDk9iCQvsKJvkzr71N81Gj6vDT/LU2P6RhY63jcafk3YJEbgeZpiFyc/5HJKv8Ef273NSfABGbQfUZhnOSAAAAAASUVORK5CYII=" alt="Jitsu Tracked"></a>
|
||||||
<img src="https://api.checklyhq.com/v1/badges/checks/5e048048-1b51-47ba-9209-60607507622e?responseTime=true" alt="Checkly Availability" />
|
<img src="https://api.checklyhq.com/v1/badges/checks/5e048048-1b51-47ba-9209-60607507622e?responseTime=true" alt="Checkly Availability" />
|
||||||
<a href="https://hub.docker.com/r/calendso/calendso"><img src="https://img.shields.io/docker/pulls/calendso/calendso"></a>
|
<a href="https://hub.docker.com/r/calcom/cal.com"><img src="https://img.shields.io/docker/pulls/calcom/cal.com"></a>
|
||||||
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
|
<a href="https://twitch.tv/calcomtv"><img src="https://img.shields.io/twitch/status/calcomtv?style=flat"></a>
|
||||||
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
|
<a href="https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22"><img src="https://img.shields.io/badge/Help%20Wanted-Contribute-blue"></a>
|
||||||
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
|
<a href="https://cal.com/figma"><img src="https://img.shields.io/badge/Figma-Design%20System-blueviolet"></a>
|
||||||
@@ -113,7 +113,7 @@ To get a local copy up and running, please follow these simple steps.
|
|||||||
Here is what you need to be able to run Cal.com.
|
Here is what you need to be able to run Cal.com.
|
||||||
|
|
||||||
- Node.js (Version: >=18.x)
|
- Node.js (Version: >=18.x)
|
||||||
- PostgreSQL
|
- PostgreSQL (Version: >=13.x)
|
||||||
- Yarn _(recommended)_
|
- Yarn _(recommended)_
|
||||||
|
|
||||||
> If you want to enable any of the available integrations, you may want to obtain additional credentials for each one. More details on this can be found below under the [integrations section](#integrations).
|
> If you want to enable any of the available integrations, you may want to obtain additional credentials for each one. More details on this can be found below under the [integrations section](#integrations).
|
||||||
@@ -210,7 +210,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
|
|||||||

|

|
||||||
|
|
||||||
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
|
||||||
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`.
|
`postgresql://postgres:postgres@localhost:5432/Your-DB-Name`. The port is configurable and does not have to be 5432.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ test.describe("Org", () => {
|
|||||||
];
|
];
|
||||||
const response = await page.goto(`http://i.cal.com/${users[0].username}+${users[1].username}`);
|
const response = await page.goto(`http://i.cal.com/${users[0].username}+${users[1].username}`);
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Dynamic");
|
expect(await page.locator('[data-testid="event-title"]').textContent()).toBe("Group Meeting");
|
||||||
|
|
||||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[0].name);
|
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[0].name);
|
||||||
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
expect(await page.locator('[data-testid="event-meta"]').textContent()).toContain(users[1].name);
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ NEXT_PUBLIC_WEBAPP_URL=http://localhost:3000
|
|||||||
|
|
||||||
# Get it in console.cal.com
|
# Get it in console.cal.com
|
||||||
CALCOM_LICENSE_KEY=""
|
CALCOM_LICENSE_KEY=""
|
||||||
|
NEXT_PUBLIC_API_V2_ROOT_URL=http://localhost:5555
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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();
|
||||||
|
};
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
import { API_KEY_RATE_LIMIT } from "@calcom/lib/rateLimit";
|
|
||||||
|
|
||||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||||
@@ -11,7 +10,7 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
|||||||
identifier: req.query.apiKey as string,
|
identifier: req.query.apiKey as string,
|
||||||
rateLimitingType: "api",
|
rateLimitingType: "api",
|
||||||
onRateLimiterResponse: (response) => {
|
onRateLimiterResponse: (response) => {
|
||||||
res.setHeader("X-RateLimit-Limit", API_KEY_RATE_LIMIT);
|
res.setHeader("X-RateLimit-Limit", response.limit);
|
||||||
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||||
res.setHeader("X-RateLimit-Reset", response.reset);
|
res.setHeader("X-RateLimit-Reset", response.reset);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IS_PRODUCTION } from "@calcom/lib/constants";
|
|||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { isAdminGuard } from "../utils/isAdmin";
|
import { isAdminGuard } from "../utils/isAdmin";
|
||||||
|
import { ScopeOfAdmin } from "../utils/scopeOfAdmin";
|
||||||
|
|
||||||
// Used to check if the apiKey is not expired, could be extracted if reused. but not for now.
|
// Used to check if the apiKey is not expired, could be extracted if reused. but not for now.
|
||||||
export const dateNotInPast = function (date: Date) {
|
export const dateNotInPast = function (date: Date) {
|
||||||
@@ -36,7 +37,10 @@ export const verifyApiKey: NextMiddleware = async (req, res, next) => {
|
|||||||
if (!apiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" });
|
if (!apiKey.userId) return res.status(404).json({ error: "No user found for this apiKey" });
|
||||||
// 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 = apiKey.userId;
|
||||||
// save the isAdmin boolean here for later use
|
const { isAdmin, scope } = await isAdminGuard(req);
|
||||||
req.isAdmin = await isAdminGuard(req);
|
|
||||||
|
req.isSystemWideAdmin = isAdmin && scope === ScopeOfAdmin.SystemWide;
|
||||||
|
req.isOrganizationOwnerOrAdmin = isAdmin && scope === ScopeOfAdmin.OrgOwnerOrAdmin;
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
@@ -3,9 +3,9 @@ import type { NextMiddleware } from "next-api-middleware";
|
|||||||
import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants";
|
import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants";
|
||||||
|
|
||||||
export const verifyCredentialSyncEnabled: NextMiddleware = async (req, res, next) => {
|
export const verifyCredentialSyncEnabled: NextMiddleware = async (req, res, next) => {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isSystemWideAdmin) {
|
||||||
return res.status(403).json({ error: "Only admin API keys can access credential syncing endpoints" });
|
return res.status(403).json({ error: "Only admin API keys can access credential syncing endpoints" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { label } from "next-api-middleware";
|
|||||||
|
|
||||||
import { addRequestId } from "./addRequestid";
|
import { addRequestId } from "./addRequestid";
|
||||||
import { captureErrors } from "./captureErrors";
|
import { captureErrors } from "./captureErrors";
|
||||||
|
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
|
||||||
import { extendRequest } from "./extendRequest";
|
import { extendRequest } from "./extendRequest";
|
||||||
import {
|
import {
|
||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
@@ -24,6 +25,7 @@ const middleware = {
|
|||||||
HTTP_POST,
|
HTTP_POST,
|
||||||
HTTP_DELETE,
|
HTTP_DELETE,
|
||||||
addRequestId,
|
addRequestId,
|
||||||
|
checkIsInMaintenanceMode,
|
||||||
verifyApiKey,
|
verifyApiKey,
|
||||||
rateLimitApiKey,
|
rateLimitApiKey,
|
||||||
extendRequest,
|
extendRequest,
|
||||||
@@ -36,6 +38,7 @@ type Middleware = keyof typeof middleware;
|
|||||||
|
|
||||||
const middlewareOrder = [
|
const middlewareOrder = [
|
||||||
// The order here, determines the order of execution
|
// The order here, determines the order of execution
|
||||||
|
"checkIsInMaintenanceMode",
|
||||||
"extendRequest",
|
"extendRequest",
|
||||||
"captureErrors",
|
"captureErrors",
|
||||||
"verifyApiKey",
|
"verifyApiKey",
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { HttpError } from "@calcom/lib/http-error";
|
|||||||
|
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
export function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
|
export function extractUserIdsFromQuery({ isSystemWideAdmin, query }: NextApiRequest) {
|
||||||
/** Guard: Only admins can query other users */
|
/** Guard: Only admins can query other users */
|
||||||
if (!isAdmin) {
|
if (!isSystemWideAdmin) {
|
||||||
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
|
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
|
||||||
}
|
}
|
||||||
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
|
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
|
||||||
|
|||||||
@@ -1,10 +1,37 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { UserPermissionRole } from "@calcom/prisma/enums";
|
import { UserPermissionRole, MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import { ScopeOfAdmin } from "./scopeOfAdmin";
|
||||||
|
|
||||||
export const isAdminGuard = async (req: NextApiRequest) => {
|
export const isAdminGuard = async (req: NextApiRequest) => {
|
||||||
const { userId } = req;
|
const { userId } = req;
|
||||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
const user = await prisma.user.findUnique({ where: { id: userId }, select: { role: true } });
|
||||||
return user?.role === UserPermissionRole.ADMIN;
|
if (!user) return { isAdmin: false, scope: null };
|
||||||
|
|
||||||
|
const { role: userRole } = user;
|
||||||
|
if (userRole === UserPermissionRole.ADMIN) return { isAdmin: true, scope: ScopeOfAdmin.SystemWide };
|
||||||
|
|
||||||
|
const orgOwnerOrAdminMemberships = await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
accepted: true,
|
||||||
|
team: {
|
||||||
|
isOrganization: true,
|
||||||
|
},
|
||||||
|
OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isOrganization: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!orgOwnerOrAdminMemberships.length) return { isAdmin: false, scope: null };
|
||||||
|
|
||||||
|
return { isAdmin: true, scope: ScopeOfAdmin.OrgOwnerOrAdmin };
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
type AccessibleUsersType = {
|
||||||
|
memberUserIds: number[];
|
||||||
|
adminUserId: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllOrganizationMemberships = async (
|
||||||
|
memberships: {
|
||||||
|
userId: number;
|
||||||
|
role: MembershipRole;
|
||||||
|
teamId: number;
|
||||||
|
}[],
|
||||||
|
orgId: number
|
||||||
|
) => {
|
||||||
|
return memberships.reduce<number[]>((acc, membership) => {
|
||||||
|
if (membership.teamId === orgId) {
|
||||||
|
acc.push(membership.userId);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllAdminMemberships = async (userId: number) => {
|
||||||
|
return await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
accepted: true,
|
||||||
|
OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
team: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isOrganization: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAllOrganizationMembers = async (organizationId: number) => {
|
||||||
|
return await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
teamId: organizationId,
|
||||||
|
accepted: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
userId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAccessibleUsers = async ({
|
||||||
|
memberUserIds,
|
||||||
|
adminUserId,
|
||||||
|
}: AccessibleUsersType): Promise<number[]> => {
|
||||||
|
const memberships = await prisma.membership.findMany({
|
||||||
|
where: {
|
||||||
|
team: {
|
||||||
|
isOrganization: true,
|
||||||
|
},
|
||||||
|
accepted: true,
|
||||||
|
OR: [
|
||||||
|
{ userId: { in: memberUserIds } },
|
||||||
|
{ userId: adminUserId, role: { in: [MembershipRole.OWNER, MembershipRole.ADMIN] } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
userId: true,
|
||||||
|
role: true,
|
||||||
|
teamId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgId = memberships.find((membership) => membership.userId === adminUserId)?.teamId;
|
||||||
|
if (!orgId) return [];
|
||||||
|
|
||||||
|
const allAccessibleMemberUserIds = await getAllOrganizationMemberships(memberships, orgId);
|
||||||
|
const accessibleUserIds = allAccessibleMemberUserIds.filter((userId) => userId !== adminUserId);
|
||||||
|
return accessibleUserIds;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const retrieveOrgScopedAccessibleUsers = async ({ adminId }: { adminId: number }) => {
|
||||||
|
const adminMemberships = await getAllAdminMemberships(adminId);
|
||||||
|
const organizationId = adminMemberships.find((membership) => membership.team.isOrganization)?.team.id;
|
||||||
|
if (!organizationId) return [];
|
||||||
|
|
||||||
|
const allMemberships = await getAllOrganizationMembers(organizationId);
|
||||||
|
return allMemberships.map((membership) => membership.userId);
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const ScopeOfAdmin = {
|
||||||
|
SystemWide: "SystemWide",
|
||||||
|
OrgOwnerOrAdmin: "OrgOwnerOrAdmin",
|
||||||
|
} as const;
|
||||||
@@ -82,4 +82,5 @@ export const schemaBookingReadPublic = Booking.extend({
|
|||||||
metadata: true,
|
metadata: true,
|
||||||
status: true,
|
status: true,
|
||||||
responses: true,
|
responses: true,
|
||||||
|
fromReschedule: true,
|
||||||
});
|
});
|
||||||
@@ -6,6 +6,9 @@ export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true })
|
|||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
pendingPayment: true,
|
pendingPayment: true,
|
||||||
|
isOrganization: true,
|
||||||
|
isPlatform: true,
|
||||||
|
smsLockState: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const schemaTeamRequiredParams = z.object({
|
const schemaTeamRequiredParams = z.object({
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ export const schemaUserBaseBodyParams = User.pick({
|
|||||||
brandColor: true,
|
brandColor: true,
|
||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
allowDynamicBooking: true,
|
allowDynamicBooking: true,
|
||||||
away: true,
|
|
||||||
role: true,
|
role: true,
|
||||||
// @note: disallowing avatar changes via API for now. We can add it later if needed. User should upload image via UI.
|
// @note: disallowing avatar changes via API for now. We can add it later if needed. User should upload image via UI.
|
||||||
// avatar: true,
|
// avatar: true,
|
||||||
@@ -171,7 +170,6 @@ export const schemaUserReadPublic = User.pick({
|
|||||||
brandColor: true,
|
brandColor: true,
|
||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
allowDynamicBooking: true,
|
allowDynamicBooking: true,
|
||||||
away: true,
|
|
||||||
createdDate: true,
|
createdDate: true,
|
||||||
verified: true,
|
verified: true,
|
||||||
invitedTo: true,
|
invitedTo: true,
|
||||||
|
|||||||
@@ -47,6 +47,22 @@ const nextConfig = {
|
|||||||
source: "/v:version/:rest*",
|
source: "/v:version/:rest*",
|
||||||
destination: "/api/v:version/:rest*",
|
destination: "/api/v:version/:rest*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/api/v2",
|
||||||
|
destination: `${process.env.NEXT_PUBLIC_API_V2_ROOT_URL}/health`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "/api/v2/health",
|
||||||
|
destination: `${process.env.NEXT_PUBLIC_API_V2_ROOT_URL}/health`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "/api/v2/docs/:path*",
|
||||||
|
destination: `${process.env.NEXT_PUBLIC_API_V2_ROOT_URL}/docs/:path*`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: "/api/v2/:path*",
|
||||||
|
destination: `${process.env.NEXT_PUBLIC_API_V2_ROOT_URL}/api/v2/:path*`,
|
||||||
|
},
|
||||||
// This redirects requests to api/v*/ to /api/ passing version as a query parameter.
|
// This redirects requests to api/v*/ to /api/ passing version as a query parameter.
|
||||||
{
|
{
|
||||||
source: "/api/v:version/:rest*",
|
source: "/api/v:version/:rest*",
|
||||||
|
|||||||
Vendored
+2
-1
@@ -11,7 +11,8 @@ export declare module "next" {
|
|||||||
method: string;
|
method: string;
|
||||||
// session: { user: { id: number } };
|
// session: { user: { id: number } };
|
||||||
// query: Partial<{ [key: string]: string | string[] }>;
|
// query: Partial<{ [key: string]: string | string[] }>;
|
||||||
isAdmin: boolean;
|
isSystemWideAdmin: boolean;
|
||||||
|
isOrganizationOwnerOrAdmin: boolean;
|
||||||
pagination: { take: number; skip: number };
|
pagination: { take: number; skip: number };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString";
|
import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString";
|
||||||
|
|
||||||
export async function authMiddleware(req: NextApiRequest) {
|
export async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdAsString.parse(req.query);
|
const { id } = schemaQueryIdAsString.parse(req.query);
|
||||||
// Admin can check any api key
|
// Admin can check any api key
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Check if user can access the api key
|
// Check if user can access the api key
|
||||||
const apiKey = await prisma.apiKey.findFirst({
|
const apiKey = await prisma.apiKey.findFirst({
|
||||||
where: { id, userId },
|
where: { id, userId },
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ type CustomNextApiRequest = NextApiRequest & {
|
|||||||
function handleAdminRequests(req: CustomNextApiRequest) {
|
function handleAdminRequests(req: CustomNextApiRequest) {
|
||||||
// To match type safety with runtime
|
// To match type safety with runtime
|
||||||
if (!hasReqArgs(req)) throw Error("Missing req.args");
|
if (!hasReqArgs(req)) throw Error("Missing req.args");
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
if (isAdmin && req.query.userId) {
|
if (isSystemWideAdmin && req.query.userId) {
|
||||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||||
req.args.where = { userId: { in: userIds } };
|
req.args.where = { userId: { in: userIds } };
|
||||||
@@ -30,8 +30,8 @@ function hasReqArgs(req: CustomNextApiRequest): req is Ensure<CustomNextApiReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getHandler(req: CustomNextApiRequest) {
|
async function getHandler(req: CustomNextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
req.args = isAdmin ? {} : { where: { userId } };
|
req.args = isSystemWideAdmin ? {} : { where: { userId } };
|
||||||
// Proof of concept: allowing mutation in exchange of composability
|
// Proof of concept: allowing mutation in exchange of composability
|
||||||
handleAdminRequests(req);
|
handleAdminRequests(req);
|
||||||
const data = await prisma.apiKey.findMany(req.args);
|
const data = await prisma.apiKey.findMany(req.args);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import prisma from "@calcom/prisma";
|
|||||||
import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key";
|
import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key";
|
||||||
|
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { neverExpires, userId: bodyUserId, ...input } = apiKeyCreateBodySchema.parse(req.body);
|
const { neverExpires, userId: bodyUserId, ...input } = apiKeyCreateBodySchema.parse(req.body);
|
||||||
const [hashedKey, apiKey] = generateUniqueAPIKey();
|
const [hashedKey, apiKey] = generateUniqueAPIKey();
|
||||||
const args: Prisma.ApiKeyCreateArgs = {
|
const args: Prisma.ApiKeyCreateArgs = {
|
||||||
@@ -24,9 +24,10 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isAdmin && bodyUserId) throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
if (!isSystemWideAdmin && bodyUserId)
|
||||||
|
throw new HttpError({ statusCode: 403, message: `ADMIN required for userId` });
|
||||||
|
|
||||||
if (isAdmin && bodyUserId) {
|
if (isSystemWideAdmin && bodyUserId) {
|
||||||
const where: Prisma.UserWhereInput = { id: bodyUserId };
|
const where: Prisma.UserWhereInput = { id: bodyUserId };
|
||||||
await prisma.user.findFirstOrThrow({ where });
|
await prisma.user.findFirstOrThrow({ where });
|
||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const query = schemaQueryIdParseInt.parse(req.query);
|
const query = schemaQueryIdParseInt.parse(req.query);
|
||||||
// @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin.
|
// @note: Here we make sure to only return attendee's of the user's own bookings if the user is not an admin.
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Find all user bookings, including attendees
|
// Find all user bookings, including attendees
|
||||||
const attendee = await prisma.attendee.findFirst({
|
const attendee = await prisma.attendee.findFirst({
|
||||||
where: { id: query.id, booking: { userId } },
|
where: { id: query.id, booking: { userId } },
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaAttendeeEditBodyParams>) {
|
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaAttendeeEditBodyParams>) {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
const { userId } = req;
|
const { userId } = req;
|
||||||
const { bookingId } = body;
|
const { bookingId } = body;
|
||||||
if (bookingId) {
|
if (bookingId) {
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ import { schemaAttendeeReadPublic } from "~/lib/validations/attendee";
|
|||||||
* description: No attendees were found
|
* description: No attendees were found
|
||||||
*/
|
*/
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const args: Prisma.AttendeeFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } };
|
const args: Prisma.AttendeeFindManyArgs = isSystemWideAdmin ? {} : { where: { booking: { userId } } };
|
||||||
const data = await prisma.attendee.findMany(args);
|
const data = await prisma.attendee.findMany(args);
|
||||||
const attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee));
|
const attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee));
|
||||||
if (!attendees) throw new HttpError({ statusCode: 404, message: "No attendees were found" });
|
if (!attendees) throw new HttpError({ statusCode: 404, message: "No attendees were found" });
|
||||||
|
|||||||
@@ -52,10 +52,10 @@ import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "~/lib/
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const body = schemaAttendeeCreateBodyParams.parse(req.body);
|
const body = schemaAttendeeCreateBodyParams.parse(req.body);
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isSystemWideAdmin) {
|
||||||
const userBooking = await prisma.booking.findFirst({
|
const userBooking = await prisma.booking.findFirst({
|
||||||
where: { userId, id: body.bookingId },
|
where: { userId, id: body.bookingId },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, query } = req;
|
const { userId, isSystemWideAdmin, query } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
/** Admins can skip the ownership verification */
|
/** Admins can skip the ownership verification */
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
/**
|
/**
|
||||||
* There's a caveat here. If the availability exists but the user doesn't own it,
|
* There's a caveat here. If the availability exists but the user doesn't own it,
|
||||||
* the user will see a 404 error which may or not be the desired behavior.
|
* the user will see a 404 error which may or not be the desired behavior.
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
const data = schemaAvailabilityCreateBodyParams.parse(req.body);
|
const data = schemaAvailabilityCreateBodyParams.parse(req.body);
|
||||||
const schedule = await prisma.schedule.findFirst({
|
const schedule = await prisma.schedule.findFirst({
|
||||||
where: { userId, id: data.scheduleId },
|
where: { userId, id: data.scheduleId },
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ const availabilitySchema = z
|
|||||||
);
|
);
|
||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { isAdmin, userId: reqUserId } = req;
|
const { isSystemWideAdmin, userId: reqUserId } = req;
|
||||||
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
|
const { username, userId, eventTypeId, dateTo, dateFrom, teamId } = availabilitySchema.parse(req.query);
|
||||||
if (!teamId)
|
if (!teamId)
|
||||||
return getUserAvailability({
|
return getUserAvailability({
|
||||||
@@ -224,7 +224,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
const isUserAdminOrOwner =
|
const isUserAdminOrOwner =
|
||||||
memberRoles[reqUserId] == MembershipRole.ADMIN ||
|
memberRoles[reqUserId] == MembershipRole.ADMIN ||
|
||||||
memberRoles[reqUserId] == MembershipRole.OWNER ||
|
memberRoles[reqUserId] == MembershipRole.OWNER ||
|
||||||
isAdmin;
|
isSystemWideAdmin;
|
||||||
if (!isUserAdminOrOwner) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
if (!isUserAdminOrOwner) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
const availabilities = members.map(async (user) => {
|
const availabilities = members.map(async (user) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||||
// Here we make sure to only return references of the user's own bookings if the user is not an admin.
|
// Here we make sure to only return references of the user's own bookings if the user is not an admin.
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Find all references where the user has bookings
|
// Find all references where the user has bookings
|
||||||
const bookingReference = await prisma.bookingReference.findFirst({
|
const bookingReference = await prisma.bookingReference.findFirst({
|
||||||
where: { id, booking: { userId } },
|
where: { id, booking: { userId } },
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
export async function patchHandler(req: NextApiRequest) {
|
export async function patchHandler(req: NextApiRequest) {
|
||||||
const { query, body, isAdmin, userId } = req;
|
const { query, body, isSystemWideAdmin, userId } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
const data = schemaBookingEditBodyParams.parse(body);
|
const data = schemaBookingEditBodyParams.parse(body);
|
||||||
/* If user tries to update bookingId, we run extra checks */
|
/* If user tries to update bookingId, we run extra checks */
|
||||||
if (data.bookingId) {
|
if (data.bookingId) {
|
||||||
const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin
|
const args: Prisma.BookingFindFirstOrThrowArgs = isSystemWideAdmin
|
||||||
? /* If admin, we only check that the booking exists */
|
? /* If admin, we only check that the booking exists */
|
||||||
{ where: { id: data.bookingId } }
|
{ where: { id: data.bookingId } }
|
||||||
: /* For non-admins we make sure the booking belongs to the user */
|
: /* For non-admins we make sure the booking belongs to the user */
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ import { schemaBookingReferenceReadPublic } from "~/lib/validations/booking-refe
|
|||||||
* description: No booking references were found
|
* description: No booking references were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const args: Prisma.BookingReferenceFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } };
|
const args: Prisma.BookingReferenceFindManyArgs = isSystemWideAdmin
|
||||||
|
? {}
|
||||||
|
: { where: { booking: { userId } } };
|
||||||
const data = await prisma.bookingReference.findMany(args);
|
const data = await prisma.bookingReference.findMany(args);
|
||||||
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
|
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ import {
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const body = schemaBookingCreateBodyParams.parse(req.body);
|
const body = schemaBookingCreateBodyParams.parse(req.body);
|
||||||
const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin
|
const args: Prisma.BookingFindFirstOrThrowArgs = isSystemWideAdmin
|
||||||
? /* If admin, we only check that the booking exists */
|
? /* If admin, we only check that the booking exists */
|
||||||
{ where: { id: body.bookingId } }
|
{ where: { id: body.bookingId } }
|
||||||
: /* For non-admins we make sure the booking belongs to the user */
|
: /* For non-admins we make sure the booking belongs to the user */
|
||||||
|
|||||||
@@ -3,15 +3,33 @@ import type { NextApiRequest } from "next";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, query } = req;
|
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin, query } = req;
|
||||||
if (isAdmin) {
|
if (isSystemWideAdmin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
|
if (isOrganizationOwnerOrAdmin) {
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: { id },
|
||||||
|
select: { userId: true },
|
||||||
|
});
|
||||||
|
if (booking) {
|
||||||
|
const bookingUserId = booking.userId;
|
||||||
|
if (bookingUserId) {
|
||||||
|
const accessibleUsersIds = await getAccessibleUsers({
|
||||||
|
adminUserId: userId,
|
||||||
|
memberUserIds: [bookingUserId],
|
||||||
|
});
|
||||||
|
if (accessibleUsersIds.length > 0) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const userWithBookingsAndTeamIds = await prisma.user.findUnique({
|
const userWithBookingsAndTeamIds = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
include: {
|
include: {
|
||||||
@@ -43,7 +61,7 @@ async function authMiddleware(req: NextApiRequest) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!teamBookings) {
|
if (!teamBookings) {
|
||||||
throw new HttpError({ statusCode: 401, message: "You are not authorized" });
|
throw new HttpError({ statusCode: 403, message: "You are not authorized" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
|||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
@@ -109,14 +110,27 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaBookingEditBodyParams>) {
|
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaBookingEditBodyParams>) {
|
||||||
const { isAdmin } = req;
|
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||||
if (body.userId && !isAdmin) {
|
if (body.userId && !isSystemWideAdmin && !isOrganizationOwnerOrAdmin) {
|
||||||
// Organizer has to be a cal user and we can't allow a booking to be transfered to some other cal user's name
|
// Organizer has to be a cal user and we can't allow a booking to be transfered to some other cal user's name
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 403,
|
statusCode: 403,
|
||||||
message: "Only admin can change the organizer of a booking",
|
message: "Only admin can change the organizer of a booking",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (body.userId && isOrganizationOwnerOrAdmin) {
|
||||||
|
const accessibleUsersIds = await getAccessibleUsers({
|
||||||
|
adminUserId: userId,
|
||||||
|
memberUserIds: [body.userId],
|
||||||
|
});
|
||||||
|
if (accessibleUsersIds.length === 0) {
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 403,
|
||||||
|
message: "Only admin can change the organizer of a booking",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(patchHandler);
|
export default defaultResponder(patchHandler);
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
|
import { getRecordingsOfCalVideoByRoomName } from "@calcom/core/videoClient";
|
||||||
|
import { getDownloadLinkOfCalVideoByRecordingId } from "@calcom/core/videoClient";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { RecordingItemSchema } from "@calcom/prisma/zod-utils";
|
||||||
|
import type { PartialReference } from "@calcom/types/EventManager";
|
||||||
|
|
||||||
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /bookings/{id}/recordings:
|
||||||
|
* get:
|
||||||
|
* summary: Find all Cal video recordings of that booking
|
||||||
|
* operationId: getRecordingsByBookingId
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* required: true
|
||||||
|
* description: ID of the booking for which recordings need to be fetched. Recording download link is only valid for 12 hours and you would have to fetch the recordings again to get new download link
|
||||||
|
* - in: query
|
||||||
|
* name: apiKey
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Your API key
|
||||||
|
* tags:
|
||||||
|
* - bookings
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* $ref: "#/components/schemas/ArrayOfRecordings"
|
||||||
|
* examples:
|
||||||
|
* recordings:
|
||||||
|
* value:
|
||||||
|
* - id: "ad90a2e7-154f-49ff-a815-5da1db7bf899"
|
||||||
|
* room_name: "0n22w24AQ5ZFOtEKX2gX"
|
||||||
|
* start_ts: 1716215386
|
||||||
|
* status: "finished"
|
||||||
|
* max_participants: 1
|
||||||
|
* duration: 11
|
||||||
|
* share_token: "x94YK-69Gnh7"
|
||||||
|
* download_link: "https://daily-meeting-recordings..."
|
||||||
|
* 401:
|
||||||
|
* description: Authorization information is missing or invalid.
|
||||||
|
* 404:
|
||||||
|
* description: Booking was not found
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getHandler(req: NextApiRequest) {
|
||||||
|
const { query } = req;
|
||||||
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
|
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { references: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `No Booking found with booking id ${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const roomName =
|
||||||
|
booking?.references?.find((reference: PartialReference) => reference.type === "daily_video")?.meetingId ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
if (!roomName)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `No Cal Video reference found with booking id ${booking.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const recordings = await getRecordingsOfCalVideoByRoomName(roomName);
|
||||||
|
|
||||||
|
if (!recordings || !("data" in recordings)) return [];
|
||||||
|
|
||||||
|
const recordingWithDownloadLink = recordings.data.map((recording: RecordingItemSchema) => {
|
||||||
|
return getDownloadLinkOfCalVideoByRecordingId(recording.id)
|
||||||
|
.then((res) => ({
|
||||||
|
...recording,
|
||||||
|
download_link: res?.download_link,
|
||||||
|
}))
|
||||||
|
.catch((err) => ({ ...recording, download_link: null, error: err.message }));
|
||||||
|
});
|
||||||
|
const res = await Promise.all(recordingWithDownloadLink);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultResponder(getHandler);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
|
||||||
|
import authMiddleware from "../_auth-middleware";
|
||||||
|
|
||||||
|
export default withMiddleware()(
|
||||||
|
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
await authMiddleware(req);
|
||||||
|
return defaultHandler({
|
||||||
|
GET: import("./_get"),
|
||||||
|
})(req, res);
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
|
import { getAllTranscriptsAccessLinkFromRoomName } from "@calcom/core/videoClient";
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { PartialReference } from "@calcom/types/EventManager";
|
||||||
|
|
||||||
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /bookings/{id}/transcripts:
|
||||||
|
* get:
|
||||||
|
* summary: Find all Cal video transcripts of that booking
|
||||||
|
* operationId: getTranscriptsByBookingId
|
||||||
|
* parameters:
|
||||||
|
* - in: path
|
||||||
|
* name: id
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* required: true
|
||||||
|
* description: ID of the booking for which recordings need to be fetched.
|
||||||
|
* - in: query
|
||||||
|
* name: apiKey
|
||||||
|
* required: true
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* description: Your API key
|
||||||
|
* tags:
|
||||||
|
* - bookings
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: OK
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* 401:
|
||||||
|
* description: Authorization information is missing or invalid.
|
||||||
|
* 404:
|
||||||
|
* description: Booking was not found
|
||||||
|
*/
|
||||||
|
|
||||||
|
export async function getHandler(req: NextApiRequest) {
|
||||||
|
const { query } = req;
|
||||||
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
|
|
||||||
|
const booking = await prisma.booking.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: { references: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!booking)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `No Booking found with booking id ${id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const roomName =
|
||||||
|
booking?.references?.find((reference: PartialReference) => reference.type === "daily_video")?.meetingId ??
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
if (!roomName)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: `No Cal Video reference found with booking id ${booking.id}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transcripts = await getAllTranscriptsAccessLinkFromRoomName(roomName);
|
||||||
|
|
||||||
|
return transcripts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultResponder(getHandler);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
|
||||||
|
import authMiddleware from "../_auth-middleware";
|
||||||
|
|
||||||
|
export default withMiddleware()(
|
||||||
|
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
await authMiddleware(req);
|
||||||
|
return defaultHandler({
|
||||||
|
GET: import("./_get"),
|
||||||
|
})(req, res);
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -5,6 +5,11 @@ import { HttpError } from "@calcom/lib/http-error";
|
|||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
import {
|
||||||
|
getAccessibleUsers,
|
||||||
|
retrieveOrgScopedAccessibleUsers,
|
||||||
|
} from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
import { schemaBookingGetParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
import { schemaBookingGetParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||||
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
@@ -108,7 +113,11 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
|||||||
* 404:
|
* 404:
|
||||||
* description: No bookings were found
|
* description: No bookings were found
|
||||||
*/
|
*/
|
||||||
|
type GetAdminArgsType = {
|
||||||
|
adminDidQueryUserIds?: boolean;
|
||||||
|
requestedUserIds: number[];
|
||||||
|
userId: number;
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* Constructs the WHERE clause for Prisma booking findMany operation.
|
* Constructs the WHERE clause for Prisma booking findMany operation.
|
||||||
*
|
*
|
||||||
@@ -161,12 +170,18 @@ function buildWhereClause(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
export async function handler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const {
|
||||||
|
userId,
|
||||||
|
isSystemWideAdmin,
|
||||||
|
isOrganizationOwnerOrAdmin,
|
||||||
|
pagination: { take, skip },
|
||||||
|
} = req;
|
||||||
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
|
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
|
||||||
|
|
||||||
const args: Prisma.BookingFindManyArgs = {};
|
const args: Prisma.BookingFindManyArgs = {};
|
||||||
|
args.take = take;
|
||||||
|
args.skip = skip;
|
||||||
args.include = {
|
args.include = {
|
||||||
attendees: true,
|
attendees: true,
|
||||||
user: true,
|
user: true,
|
||||||
@@ -182,19 +197,32 @@ async function handler(req: NextApiRequest) {
|
|||||||
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||||
|
|
||||||
/** Only admins can query other users */
|
/** Only admins can query other users */
|
||||||
if (isAdmin) {
|
if (isSystemWideAdmin) {
|
||||||
if (req.query.userId) {
|
if (req.query.userId || filterByAttendeeEmails) {
|
||||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
const requestedUserIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||||
const users = await prisma.user.findMany({
|
|
||||||
where: { id: { in: userIds } },
|
const systemWideAdminArgs = {
|
||||||
select: { email: true },
|
adminDidQueryUserIds: !!req.query.userId,
|
||||||
});
|
requestedUserIds,
|
||||||
const userEmails = users.map((u) => u.email);
|
userId,
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
};
|
||||||
} else if (filterByAttendeeEmails) {
|
const { userId: argUserId, userIds, userEmails } = await handleSystemWideAdminArgs(systemWideAdminArgs);
|
||||||
args.where = buildWhereClause(null, attendeeEmails, [], []);
|
args.where = buildWhereClause(argUserId, attendeeEmails, userIds, userEmails);
|
||||||
}
|
}
|
||||||
|
} else if (isOrganizationOwnerOrAdmin) {
|
||||||
|
let requestedUserIds = [userId];
|
||||||
|
if (req.query.userId || filterByAttendeeEmails) {
|
||||||
|
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||||
|
requestedUserIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||||
|
}
|
||||||
|
const orgWideAdminArgs = {
|
||||||
|
adminDidQueryUserIds: !!req.query.userId,
|
||||||
|
requestedUserIds,
|
||||||
|
userId,
|
||||||
|
};
|
||||||
|
const { userId: argUserId, userIds, userEmails } = await handleOrgWideAdminArgs(orgWideAdminArgs);
|
||||||
|
args.where = buildWhereClause(argUserId, attendeeEmails, userIds, userEmails);
|
||||||
} else {
|
} else {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
@@ -203,7 +231,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpError({ message: "User not found", statusCode: 500 });
|
throw new HttpError({ message: "User not found", statusCode: 404 });
|
||||||
}
|
}
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||||
}
|
}
|
||||||
@@ -225,4 +253,53 @@ async function handler(req: NextApiRequest) {
|
|||||||
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(handler);
|
const handleSystemWideAdminArgs = async ({
|
||||||
|
adminDidQueryUserIds,
|
||||||
|
requestedUserIds,
|
||||||
|
userId,
|
||||||
|
}: GetAdminArgsType) => {
|
||||||
|
if (adminDidQueryUserIds) {
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: { id: { in: requestedUserIds } },
|
||||||
|
select: { email: true },
|
||||||
|
});
|
||||||
|
const userEmails = users.map((u) => u.email);
|
||||||
|
|
||||||
|
return { userId, userIds: requestedUserIds, userEmails };
|
||||||
|
}
|
||||||
|
return { userId: null, userIds: [], userEmails: [] };
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOrgWideAdminArgs = async ({
|
||||||
|
adminDidQueryUserIds,
|
||||||
|
requestedUserIds,
|
||||||
|
userId,
|
||||||
|
}: GetAdminArgsType) => {
|
||||||
|
if (adminDidQueryUserIds) {
|
||||||
|
const accessibleUsersIds = await getAccessibleUsers({
|
||||||
|
adminUserId: userId,
|
||||||
|
memberUserIds: requestedUserIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!accessibleUsersIds.length) throw new HttpError({ message: "No User found", statusCode: 404 });
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: { id: { in: accessibleUsersIds } },
|
||||||
|
select: { email: true },
|
||||||
|
});
|
||||||
|
const userEmails = users.map((u) => u.email);
|
||||||
|
return { userId, userIds: accessibleUsersIds, userEmails };
|
||||||
|
} else {
|
||||||
|
const accessibleUsersIds = await retrieveOrgScopedAccessibleUsers({
|
||||||
|
adminId: userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const users = await prisma.user.findMany({
|
||||||
|
where: { id: { in: accessibleUsersIds } },
|
||||||
|
select: { email: true },
|
||||||
|
});
|
||||||
|
const userEmails = users.map((u) => u.email);
|
||||||
|
return { userId, userIds: accessibleUsersIds, userEmails };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withMiddleware("pagination")(defaultResponder(handler));
|
||||||
@@ -4,6 +4,8 @@ import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBooking
|
|||||||
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /bookings:
|
* /bookings:
|
||||||
@@ -204,8 +206,17 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||||
if (isAdmin) req.userId = req.body.userId || userId;
|
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||||
|
|
||||||
|
if (isOrganizationOwnerOrAdmin) {
|
||||||
|
const accessibleUsersIds = await getAccessibleUsers({
|
||||||
|
adminUserId: userId,
|
||||||
|
memberUserIds: [req.body.userId || userId],
|
||||||
|
});
|
||||||
|
const [requestedUserId] = accessibleUsersIds;
|
||||||
|
req.userId = requestedUserId || userId;
|
||||||
|
}
|
||||||
|
|
||||||
return await handleNewBooking(req, getBookingDataSchemaForApi);
|
return await handleNewBooking(req, getBookingDataSchemaForApi);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,9 +97,10 @@ import { schemaConnectedCalendarsReadPublic } from "~/lib/validations/connected-
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
|
|
||||||
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
if (!isSystemWideAdmin && req.query.userId)
|
||||||
|
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
||||||
|
|
||||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { minimumTokenResponseSchema } from "@calcom/app-store/_utils/oauth/parseRefreshTokenResponse";
|
import { OAuth2UniversalSchema } from "@calcom/app-store/_utils/oauth/universalSchema";
|
||||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
@@ -63,7 +63,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
symmetricDecrypt(encryptedKey, process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY || "")
|
symmetricDecrypt(encryptedKey, process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY || "")
|
||||||
);
|
);
|
||||||
|
|
||||||
const key = minimumTokenResponseSchema.parse(decryptedKey);
|
const key = OAuth2UniversalSchema.parse(decryptedKey);
|
||||||
|
|
||||||
const credential = await prisma.credential.update({
|
const credential = await prisma.credential.update({
|
||||||
where: {
|
where: {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
import { getCalendar } from "@calcom/app-store/_utils/getCalendar";
|
||||||
import { minimumTokenResponseSchema } from "@calcom/app-store/_utils/oauth/parseRefreshTokenResponse";
|
import { OAuth2UniversalSchema } from "@calcom/app-store/_utils/oauth/universalSchema";
|
||||||
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
||||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
@@ -70,7 +70,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
symmetricDecrypt(encryptedKey, process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY || "")
|
symmetricDecrypt(encryptedKey, process.env.CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY || "")
|
||||||
);
|
);
|
||||||
|
|
||||||
const key = minimumTokenResponseSchema.parse(decryptedKey);
|
const key = OAuth2UniversalSchema.parse(decryptedKey);
|
||||||
|
|
||||||
// Need to get app type
|
// Need to get app type
|
||||||
const app = await prisma.app.findUnique({
|
const app = await prisma.app.findUnique({
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||||
// Admins can just skip this check
|
// Admins can just skip this check
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Check if the current user can access the event type of this input
|
// Check if the current user can access the event type of this input
|
||||||
const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({
|
const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({
|
||||||
where: { id, eventType: { userId } },
|
where: { id, eventType: { userId } },
|
||||||
|
|||||||
@@ -29,8 +29,10 @@ import { schemaEventTypeCustomInputPublic } from "~/lib/validations/event-type-c
|
|||||||
* description: No eventTypeCustomInputs were found
|
* description: No eventTypeCustomInputs were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const args: Prisma.EventTypeCustomInputFindManyArgs = isAdmin ? {} : { where: { eventType: { userId } } };
|
const args: Prisma.EventTypeCustomInputFindManyArgs = isSystemWideAdmin
|
||||||
|
? {}
|
||||||
|
: { where: { eventType: { userId } } };
|
||||||
const data = await prisma.eventTypeCustomInput.findMany(args);
|
const data = await prisma.eventTypeCustomInput.findMany(args);
|
||||||
return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) };
|
return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,10 +80,10 @@ import {
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { eventTypeId, ...body } = schemaEventTypeCustomInputBodyParams.parse(req.body);
|
const { eventTypeId, ...body } = schemaEventTypeCustomInputBodyParams.parse(req.body);
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isSystemWideAdmin) {
|
||||||
/* We check that the user has access to the event type he's trying to add a custom input to. */
|
/* We check that the user has access to the event type he's trying to add a custom input to. */
|
||||||
const eventType = await prisma.eventType.findFirst({
|
const eventType = await prisma.eventType.findFirst({
|
||||||
where: { id: eventTypeId, userId },
|
where: { id: eventTypeId, userId },
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import prisma from "@calcom/prisma";
|
|||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
const userEventTypes = await prisma.eventType.findMany({
|
const userEventTypes = await prisma.eventType.findMany({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
select: { id: true },
|
select: { id: true },
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ type UserCredentialType = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function patchHandler(req: NextApiRequest) {
|
export async function patchHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, query, body } = req;
|
const { userId, isSystemWideAdmin, query, body } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(query);
|
const { id } = schemaQueryIdParseInt.parse(query);
|
||||||
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
|
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
|
||||||
const assignedUserId = isAdmin ? parsedBody.userId || userId : userId;
|
const assignedUserId = isSystemWideAdmin ? parsedBody.userId || userId : userId;
|
||||||
|
|
||||||
validateIntegrationInput(parsedBody);
|
validateIntegrationInput(parsedBody);
|
||||||
const destinationCalendarObject: DestinationCalendarType = await getDestinationCalendar(id, prisma);
|
const destinationCalendarObject: DestinationCalendarType = await getDestinationCalendar(id, prisma);
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ import {
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, body } = req;
|
const { userId, isSystemWideAdmin, body } = req;
|
||||||
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
|
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
|
||||||
await checkPermissions(req, userId);
|
await checkPermissions(req, userId);
|
||||||
|
|
||||||
const assignedUserId = isAdmin && parsedBody.userId ? parsedBody.userId : userId;
|
const assignedUserId = isSystemWideAdmin && parsedBody.userId ? parsedBody.userId : userId;
|
||||||
|
|
||||||
/* Check if credentialId data matches the ownership and integration passed in */
|
/* Check if credentialId data matches the ownership and integration passed in */
|
||||||
const userCredentials = await prisma.credential.findMany({
|
const userCredentials = await prisma.credential.findMany({
|
||||||
@@ -120,19 +120,20 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest, userId: number) {
|
async function checkPermissions(req: NextApiRequest, userId: number) {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
const body = schemaDestinationCalendarCreateBodyParams.parse(req.body);
|
const body = schemaDestinationCalendarCreateBodyParams.parse(req.body);
|
||||||
|
|
||||||
/* Non-admin users can only create destination calendars for themselves */
|
/* Non-admin users can only create destination calendars for themselves */
|
||||||
if (!isAdmin && body.userId)
|
if (!isSystemWideAdmin && body.userId)
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
message: "ADMIN required for `userId`",
|
message: "ADMIN required for `userId`",
|
||||||
});
|
});
|
||||||
/* Admin users are required to pass in a userId */
|
/* Admin users are required to pass in a userId */
|
||||||
if (isAdmin && !body.userId) throw new HttpError({ statusCode: 400, message: "`userId` required" });
|
if (isSystemWideAdmin && !body.userId)
|
||||||
|
throw new HttpError({ statusCode: 400, message: "`userId` required" });
|
||||||
/* User should only be able to create for their own destination calendars*/
|
/* User should only be able to create for their own destination calendars*/
|
||||||
if (!isAdmin && body.eventTypeId) {
|
if (!isSystemWideAdmin && body.eventTypeId) {
|
||||||
const ownsEventType = await prisma.eventType.findFirst({ where: { id: body.eventTypeId, userId } });
|
const ownsEventType = await prisma.eventType.findFirst({ where: { id: body.eventTypeId, userId } });
|
||||||
if (!ownsEventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
if (!ownsEventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,37 @@ const swaggerHandler = withSwagger({
|
|||||||
$ref: "#/components/schemas/Booking",
|
$ref: "#/components/schemas/Booking",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
ArrayOfRecordings: {
|
||||||
|
type: "array",
|
||||||
|
items: {
|
||||||
|
$ref: "#/components/schemas/Recording",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Recording: {
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
room_name: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
start_ts: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
max_participants: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
download_link: {
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
Booking: {
|
Booking: {
|
||||||
properties: {
|
properties: {
|
||||||
id: {
|
id: {
|
||||||
@@ -57,6 +88,11 @@ const swaggerHandler = withSwagger({
|
|||||||
type: "string",
|
type: "string",
|
||||||
example: "Europe/London",
|
example: "Europe/London",
|
||||||
},
|
},
|
||||||
|
fromReschedule: {
|
||||||
|
type: "string",
|
||||||
|
nullable: true,
|
||||||
|
format: "uuid",
|
||||||
|
},
|
||||||
attendees: {
|
attendees: {
|
||||||
type: "array",
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { NextApiRequest } from "next";
|
|
||||||
|
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
|
|
||||||
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
|
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
|
||||||
const { userId, isAdmin } = req;
|
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
|
||||||
if (isAdmin) return;
|
|
||||||
const eventType = await prisma.eventType.findFirst({
|
|
||||||
where: { id, users: { some: { id: userId } } },
|
|
||||||
});
|
|
||||||
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
|
||||||
}
|
|
||||||
|
|
||||||
export default authMiddleware;
|
|
||||||
@@ -46,9 +46,9 @@ export async function deleteHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
/** Only event type owners can delete it */
|
/** Only event type owners can delete it */
|
||||||
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
|
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
|
||||||
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
|
|||||||
@@ -87,12 +87,13 @@ async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
|
|||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
eventType: (T & Partial<Omit<T, keyof BaseEventTypeCheckPermissions>>) | null
|
eventType: (T & Partial<Omit<T, keyof BaseEventTypeCheckPermissions>>) | null
|
||||||
) {
|
) {
|
||||||
if (req.isAdmin) return true;
|
if (req.isSystemWideAdmin) return true;
|
||||||
if (eventType?.teamId) {
|
if (eventType?.teamId) {
|
||||||
req.query.teamId = String(eventType.teamId);
|
req.query.teamId = String(eventType.teamId);
|
||||||
await canAccessTeamEventOrThrow(req, {
|
await canAccessTeamEventOrThrow(req, {
|
||||||
in: [MembershipRole.OWNER, MembershipRole.ADMIN, MembershipRole.MEMBER],
|
in: [MembershipRole.OWNER, MembershipRole.ADMIN, MembershipRole.MEMBER],
|
||||||
});
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (eventType?.userId === req.userId) return true; // is owner.
|
if (eventType?.userId === req.userId) return true; // is owner.
|
||||||
throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
|
|||||||
@@ -237,9 +237,9 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaEventTypeBaseBodyParams>) {
|
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaEventTypeBaseBodyParams>) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { id } = schemaQueryIdParseInt.parse(req.query);
|
const { id } = schemaQueryIdParseInt.parse(req.query);
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
/** Only event type owners can modify it */
|
/** Only event type owners can modify it */
|
||||||
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
|
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
|
||||||
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
|||||||
|
|
||||||
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
|
||||||
import authMiddleware from "./_auth-middleware";
|
|
||||||
|
|
||||||
export default withMiddleware()(
|
export default withMiddleware()(
|
||||||
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
|
defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
await authMiddleware(req);
|
|
||||||
return defaultHandler({
|
return defaultHandler({
|
||||||
GET: import("./_get"),
|
GET: import("./_get"),
|
||||||
PATCH: import("./_patch"),
|
PATCH: import("./_patch"),
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ import getCalLink from "./_utils/getCalLink";
|
|||||||
* description: No event types were found
|
* description: No event types were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||||
const { slug } = schemaQuerySlug.parse(req.query);
|
const { slug } = schemaQuerySlug.parse(req.query);
|
||||||
const shouldUseUserId = !isAdmin || !slug || !!req.query.userId;
|
const shouldUseUserId = !isSystemWideAdmin || !slug || !!req.query.userId;
|
||||||
// When user is admin and no query params are provided we should return all event types.
|
// When user is admin and no query params are provided we should return all event types.
|
||||||
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
|
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
|
||||||
const data = await prisma.eventType.findMany({
|
const data = await prisma.eventType.findMany({
|
||||||
@@ -74,9 +74,9 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
// TODO: Extract & reuse.
|
// TODO: Extract & reuse.
|
||||||
function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
|
function extractUserIdsFromQuery({ isSystemWideAdmin, query }: NextApiRequest) {
|
||||||
/** Guard: Only admins can query other users */
|
/** Guard: Only admins can query other users */
|
||||||
if (!isAdmin) {
|
if (!isSystemWideAdmin) {
|
||||||
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
|
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
|
||||||
}
|
}
|
||||||
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
|
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, body } = req;
|
const { userId, isSystemWideAdmin, body } = req;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
hosts = [],
|
hosts = [],
|
||||||
@@ -291,7 +291,7 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
await checkUserMembership(req);
|
await checkUserMembership(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAdmin && parsedBody.userId) {
|
if (isSystemWideAdmin && parsedBody.userId) {
|
||||||
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
|
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,18 +311,18 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
const body = schemaEventTypeCreateBodyParams.parse(req.body);
|
const body = schemaEventTypeCreateBodyParams.parse(req.body);
|
||||||
/* Non-admin users can only create event types for themselves */
|
/* Non-admin users can only create event types for themselves */
|
||||||
if (!isAdmin && body.userId)
|
if (!isSystemWideAdmin && body.userId)
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 401,
|
statusCode: 401,
|
||||||
message: "ADMIN required for `userId`",
|
message: "ADMIN required for `userId`",
|
||||||
});
|
});
|
||||||
if (
|
if (
|
||||||
body.teamId &&
|
body.teamId &&
|
||||||
!isAdmin &&
|
!isSystemWideAdmin &&
|
||||||
!(await canUserAccessTeamWithRole(req.userId, isAdmin, body.teamId, {
|
!(await canUserAccessTeamWithRole(req.userId, isSystemWideAdmin, body.teamId, {
|
||||||
in: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
in: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
@@ -331,7 +331,7 @@ async function checkPermissions(req: NextApiRequest) {
|
|||||||
message: "ADMIN required for `teamId`",
|
message: "ADMIN required for `teamId`",
|
||||||
});
|
});
|
||||||
/* Admin users are required to pass in a userId or teamId */
|
/* Admin users are required to pass in a userId or teamId */
|
||||||
if (isAdmin && !body.userId && !body.teamId)
|
if (isSystemWideAdmin && !body.userId && !body.teamId)
|
||||||
throw new HttpError({ statusCode: 400, message: "`userId` or `teamId` required" });
|
throw new HttpError({ statusCode: 400, message: "`userId` or `teamId` required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ export default async function checkTeamEventEditPermission(
|
|||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
body: Pick<z.infer<typeof schemaEventTypeCreateBodyParams>, "teamId" | "userId">
|
body: Pick<z.infer<typeof schemaEventTypeCreateBodyParams>, "teamId" | "userId">
|
||||||
) {
|
) {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
let userId = req.userId;
|
let userId = req.userId;
|
||||||
if (isAdmin && body.userId) {
|
if (isSystemWideAdmin && body.userId) {
|
||||||
userId = body.userId;
|
userId = body.userId;
|
||||||
}
|
}
|
||||||
if (body.teamId) {
|
if (body.teamId) {
|
||||||
@@ -26,7 +26,7 @@ export default async function checkTeamEventEditPermission(
|
|||||||
|
|
||||||
if (!membership?.role || !["ADMIN", "OWNER"].includes(membership.role)) {
|
if (!membership?.role || !["ADMIN", "OWNER"].includes(membership.role)) {
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
statusCode: 401,
|
statusCode: 403,
|
||||||
message: "No permission to operate on event-type for this team",
|
message: "No permission to operate on event-type for this team",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
|
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// To prevent auto-accepted invites, limit it to ADMIN users
|
// To prevent auto-accepted invites, limit it to ADMIN users
|
||||||
if (!isAdmin && "accepted" in body)
|
if (!isSystemWideAdmin && "accepted" in body)
|
||||||
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
||||||
// Only team OWNERS and ADMINS can add other members
|
// Only team OWNERS and ADMINS can add other members
|
||||||
const membership = await prisma.membership.findFirst({
|
const membership = await prisma.membership.findFirst({
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
|
|||||||
import { membershipIdSchema } from "~/lib/validations/membership";
|
import { membershipIdSchema } from "~/lib/validations/membership";
|
||||||
|
|
||||||
async function authMiddleware(req: NextApiRequest) {
|
async function authMiddleware(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { teamId } = membershipIdSchema.parse(req.query);
|
const { teamId } = membershipIdSchema.parse(req.query);
|
||||||
// Admins can just skip this check
|
// Admins can just skip this check
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Only team members can modify a membership
|
// Only team members can modify a membership
|
||||||
const membership = await prisma.membership.findFirst({ where: { userId, teamId } });
|
const membership = await prisma.membership.findFirst({ where: { userId, teamId } });
|
||||||
if (!membership) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
if (!membership) throw new HttpError({ statusCode: 403, message: "Forbidden" });
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ export async function deleteHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { isAdmin, userId, query } = req;
|
const { isSystemWideAdmin, userId, query } = req;
|
||||||
const userId_teamId = membershipIdSchema.parse(query);
|
const userId_teamId = membershipIdSchema.parse(query);
|
||||||
// Admin User can do anything including deletion of Admin Team Member in any team
|
// Admin User can do anything including deletion of Admin Team Member in any team
|
||||||
if (isAdmin) {
|
if (isSystemWideAdmin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
const { userId: queryUserId, teamId } = membershipIdSchema.parse(req.query);
|
const { userId: queryUserId, teamId } = membershipIdSchema.parse(req.query);
|
||||||
const data = membershipEditBodySchema.parse(req.body);
|
const data = membershipEditBodySchema.parse(req.body);
|
||||||
// Admins can just skip this check
|
// Admins can just skip this check
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
// Only the invited user can accept the invite
|
// Only the invited user can accept the invite
|
||||||
if ("accepted" in data && queryUserId !== userId)
|
if ("accepted" in data && queryUserId !== userId)
|
||||||
throw new HttpError({
|
throw new HttpError({
|
||||||
|
|||||||
@@ -46,26 +46,28 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
* Returns requested users IDs only if admin, otherwise return only current user ID
|
* Returns requested users IDs only if admin, otherwise return only current user ID
|
||||||
*/
|
*/
|
||||||
function getUserIds(req: NextApiRequest) {
|
function getUserIds(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
/** Only admins can query other users */
|
/** Only admins can query other users */
|
||||||
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
if (!isSystemWideAdmin && req.query.userId)
|
||||||
if (isAdmin && req.query.userId) {
|
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
||||||
|
if (isSystemWideAdmin && req.query.userId) {
|
||||||
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
|
||||||
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
|
||||||
return userIds;
|
return userIds;
|
||||||
}
|
}
|
||||||
// Return all memberships for ADMIN, limit to current user to non-admins
|
// Return all memberships for ADMIN, limit to current user to non-admins
|
||||||
return isAdmin ? undefined : [userId];
|
return isSystemWideAdmin ? undefined : [userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns requested teams IDs only if admin
|
* Returns requested teams IDs only if admin
|
||||||
*/
|
*/
|
||||||
function getTeamIds(req: NextApiRequest) {
|
function getTeamIds(req: NextApiRequest) {
|
||||||
const { isAdmin } = req;
|
const { isSystemWideAdmin } = req;
|
||||||
/** Only admins can query other teams */
|
/** Only admins can query other teams */
|
||||||
if (!isAdmin && req.query.teamId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
if (!isSystemWideAdmin && req.query.teamId)
|
||||||
if (isAdmin && req.query.teamId) {
|
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
|
||||||
|
if (isSystemWideAdmin && req.query.teamId) {
|
||||||
const query = schemaQuerySingleOrMultipleTeamIds.parse(req.query);
|
const query = schemaQuerySingleOrMultipleTeamIds.parse(req.query);
|
||||||
const teamIds = Array.isArray(query.teamId) ? query.teamId : [query.teamId];
|
const teamIds = Array.isArray(query.teamId) ? query.teamId : [query.teamId];
|
||||||
return teamIds;
|
return teamIds;
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function checkPermissions(req: NextApiRequest) {
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
const { userId, isAdmin } = req;
|
const { userId, isSystemWideAdmin } = req;
|
||||||
if (isAdmin) return;
|
if (isSystemWideAdmin) return;
|
||||||
const body = membershipCreateBodySchema.parse(req.body);
|
const body = membershipCreateBodySchema.parse(req.body);
|
||||||
// To prevent auto-accepted invites, limit it to ADMIN users
|
// To prevent auto-accepted invites, limit it to ADMIN users
|
||||||
if (!isAdmin && "accepted" in body)
|
if (!isSystemWideAdmin && "accepted" in body)
|
||||||
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
||||||
// Only team OWNERS and ADMINS can add other members
|
// Only team OWNERS and ADMINS can add other members
|
||||||
const membership = await prisma.membership.findFirst({
|
const membership = await prisma.membership.findFirst({
|
||||||
|
|||||||
Loaded 100 of 1344 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user