Compare commits

...
179 Commits
Author SHA1 Message Date
Alex van Andel a7b5fd593e chore: Release version 4.1.2 (#15159)
release.yml / release (push) Canceled after 0s
2024-05-22 15:46:08 +00:00
Udit Takkar 6dd4933337 feat: add get recording and transcripts API endpoint (#15131)
* feat: add get recording API endpoint

* feat: add transcript endpoint

* chore: add missing await

* chore: add error handling

* fix: type err and add swagger comment

* test: add test

* chore: add afterEach

* fix: test

* test: add transcript test
2024-05-22 14:04:08 +01:00
Hariom Balhara 4217efc5a8 fix: Failing futureLimit handleNewBooking test (#15155) 2024-05-22 14:44:56 +05:30
aca2b29aa4 feat: better logging for failed webhooks (#14825)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2024-05-22 08:51:14 +00:00
Omar López a18807dbc2 chore: v4.1.1 (#15151)
release.yml / release (push) Canceled after 0s
2024-05-21 23:03:31 +00:00
bd1664ad6e feat: Add Organization Admin scope to API V1 (#14860)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Syed Ali Shahbaz <syedshahbaz@Syeds-MacBook-Pro.local>
Co-authored-by: zomars <zomars@me.com>
2024-05-21 20:09:20 +00:00
Omar López 23cbd838c2 chore: skips flaky future limits test (#15149) 2024-05-21 19:33:09 +00:00
a4a1c016dc change redirect location for teams and organization pages (#15106)
Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-05-21 18:40:26 +00:00
Keith Williams 1b176f0d49 chore: Add integration tests runner (#15148) 2024-05-21 17:49:17 +00:00
Alex van Andelandsean-brydon 99d4461dcc fix: Add organization logo to org dynamic events (#15082)
* fix: Add organization logo to org dynamic events

* Slow typescript strikes again

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-05-21 18:28:09 +01:00
Keith Williams 7d7e0ae6a7 chore: Downgrade 8 vCPU actions to 4 (#15147) 2024-05-21 17:10:36 +00:00
Lauris Skraucis c3638eb990 refactor: v2 origin check error message (#15143) 2024-05-21 15:35:27 +00:00
Amir Elemam cfc157b665 feat(i18n): finish pt-br translation (#15141) 2024-05-21 16:07:23 +02:00
Udit TakkarandPeer Richelsen 96bf81ce8a fix: return 200 status code (#15134)
* fix: return 200 status code

* chore: undo padding in transcribe

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-05-21 07:18:00 +00:00
Omar López 0b9133c2cf fix: Adds missing dependecy (#15139) 2024-05-21 01:04:40 +00:00
bd6c471652 feat: Workflow — Send Email to Specific Email (#14815)
* feat: Workflow — Send Email to Specific Email

* disable button when verifyEmail pending

* add verified emails table and routes

* fix: include teamId in verifyEmailCode

* fix: include email address in activate handler

* fix: check team before adding email

* feat: check verified email, add to team if only users

* remove logs

* fix: if statement

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

* change email to verify your email

* fix: show error if saving unverified email

* fix: show email in subject

* verify email when editing steps

* remove double calls from same block

* verify email when creating new step

---------

Co-authored-by: v0ltZzie <161201747+v0ltZzie@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-20 21:23:19 +00:00
Lauris Skraucis 5ee3744015 Revert "refactor: api v2 schedules (#14870)" (#15130)
This reverts commit c25c835b76.
2024-05-20 16:26:48 +00:00
Rajiv Sahal 98235d00c8 fix: onboarding flow styling (#15111)
* fix sidebar styling

* platform pricing style fixes
2024-05-20 14:09:59 +01:00
Peer RichelsenandRyukemeister 5c2d2deb82 chore: hide consumer navigation in platform admin dropdown (#15117)
* hide consumer navigation in platform admin dropdown

* fix imports

* fix typings

---------

Co-authored-by: Ryukemeister <sahalrajiv-extc@atharvacoe.ac.in>
2024-05-20 14:09:45 +01:00
Alex van Andel 36fa8854ad chore: Updated labeler to v5 (#15127) 2024-05-20 14:08:50 +01:00
56022a1ba8 feat: Future Limit - Toggle to exclude unavailable days (#14915)
* wip

* wip

* wip

* Add tests

* Update getSchedule.test.ts

* Update types.ts

* Self review fixes

* Update text

* PR feedback from Carina

* Use utcoffset for range as well

* Remove logging from the function that runs on every timeslot

* Add one more test

* Show x days instead of x+1 days for both ROLLING and ROLLING_WINDOW

* refactor tests

* Move bookingScenario imports to the top as they import prismock mock that has to be imported very early

* More reordering of imports

* Fix accidental min update at wrong place

* Handle legacy value of zero for periodDays

* Range fix

* Fix isDateOutOfBound not being checked properly during booking. Also added a test for the case

* Fix duplicate element, how the hell it reached there

* Use today plus x days for ROLLING periodType

* Add disabled for Checkbox as well

* Revert logger.ts

* Formatting

Signed-off-by: zomars <zomars@me.com>

* Fix ordering of imports that is causing tests failure

---------

Signed-off-by: zomars <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-20 13:41:07 +01:00
Alex van Andel b67d37fac7 Merge branch 'fixed_labeler_issue' 2024-05-20 12:55:26 +01:00
Udit Takkar 615a89737d fix: daily buffer in getServerSideProps (#15093) 2024-05-20 12:52:38 +01:00
Alex van Andel d72c814999 Merge branch 'main' into fixed_labeler_issue 2024-05-20 12:40:35 +01:00
Udit Takkar 124471ef4a feat: add autoscroll in mobile (#15124) 2024-05-20 13:27:13 +02:00
Peer Richelsen 96e93138e7 chore: fixed a few cal.ai ui issues and cal.video logo (#15044) 2024-05-20 14:10:13 +05:30
Peer Richelsen f28c2645db chore: added google reviews (#15113) 2024-05-20 09:43:06 +02:00
Anik Dhabal Babu 650399ddc7 fix: resolve labeler workflow failure 2024-05-18 20:15:19 +00:00
64ad1c8d5b rate limiting fix (#15095)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-18 10:12:07 +00:00
Utkersh RajvenshiandKeith Williams 2714368a90 chore: API Build cache in CI pipeline (#14929)
* Added cache retrieval steps for api v1, v2 in production builds workflow run

* Removed .turbo and .next specific arguments as path in v2 API build

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-17 22:12:47 +00:00
Anik Dhabal Babu 9bcd1d3609 fix: upgrade deprecated github actions (#14998)
* fix: upgrade depricated github actions

* Update nextjs-bundle-analysis.yml
2024-05-17 21:52:22 +00:00
Morgan b3fab3aacd chore: api-v2 slotInterval, afterEventBuffer, beforeEventBuffer min 0 (#15039) 2024-05-17 22:22:19 +01:00
Omar López e3741f3e06 fix: Prevent 500 error when fetching more than 3000 bookings from an user (#15099) 2024-05-17 19:07:55 +00:00
ae4e0f8893 chore: Add test for booking cancelled webhook (#14940)
* chore: Add test for booking cancelled webhook

* added unit test

* assertions to check booking created successfully

* remove booking cancelled e2e

* test: add webhook cancelled test

* chore: remove old test

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-05-17 13:56:17 +00:00
9c64e8dc28 fix: Collective event types with seats - hosts count towards the seat count (#14930)
* fix: Collective event types with seats - hosts count towards the seat count

* refactor getCurrentSeats

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-17 13:46:05 +00:00
Hariom BalharaandUdit Takkar bbb86f2174 fix: Invalid phone error on prefill with plus(+) sign (#15046)
* fix: Invalid phone error

* Update packages/features/bookings/lib/getBookingResponsesSchema.test.ts

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-17 13:30:47 +00:00
Rajiv SahalandMorgan a559daa076 fix: disable emails for platform (#15089)
* disable emails for platform

* check for if user is platform and skip verification accordingly

* skip isPlatform check for verify email in create org handler

* use if instead of &&

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-05-17 18:07:04 +05:30
Somay ChauhanandUdit Takkar e723b989a8 fix: Add the 'cal-video' location only if it doesn't already exist (#15038)
* fix: Add the 'cal-video' location only if it doesn't already exist

* refactor: improve code

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-05-17 09:13:56 +00:00
Udit Takkar 490540ac56 fix: terms and conditions text (#15027) 2024-05-16 14:54:22 +01:00
Lauris Skraucis c25c835b76 refactor: api v2 schedules (#14870)
* api: createSchedule refactor - still need to handle overrides

* createSchedule: handle overrides

* createSchedule: default schedule e2e test fix and pad hours:minutes

* get default schedule, by id and all schedules

* update schedule

* refactor: update handler delete only if overrides or availabilities given

* remove comment

* refactor: move schedules inputs, outputs to platform-types + e2e tests refactor

* refactor e2e

* fix: overrides and test

* fix managed user create

* my brain is fried but refactor good

* WIP: transform api schedule for atom

* WIP: transform api schedule for atom

* WIP: transform api schedule for atom

* fix: re-export atom transformer from lib

* fix: format schedule for api from atom

* fix ts ignore

* refactor: move schedule hooks into hooks/schedules

* feat: useSchedules hook to get count of user schedules

* tests: setup atom tests & test transformScheduleForAtom

* rename transformers

* test transformAtomScheduleForApi

* reset scheduel update handler for trpc

* fix: import path

* refactor: transformers

* swagger docs fix

* get rid of unused availabilities module

* get rid of eslint-disable-next-line in schedules repository

* refactor: remove crypto from atoms vite rollupOptions

* refactor: comment schedules repository update logic
2024-05-16 15:20:13 +03:00
Peer Richelsen ca05e701a8 feat: added BAA to App Store (#14894)
* added BAA to App Store

* added price
2024-05-16 13:06:12 +01:00
Rajiv Sahal 4541f0d714 fix: platform onboarding improvements (#15053)
* allow stripe to display promo codes option

* fix layout issues for tablet devices

* redirect platform users to new platform onboarding
2024-05-16 10:51:20 +03:00
Alex van Andel 0ec5153f14 v4.1.0 (#15060)
release.yml / release (push) Canceled after 0s
2024-05-15 14:11:36 -04:00
Anik Dhabal BabuandCarina Wollendorfer 90ce0f8d4d fix: hosts not set when duplicating team event-type (#15012)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-15 18:07:22 +00:00
Amit Sharma 5e9902921f fix: Dynamic (Group) meetings showing one person avatar on the double (#15055) 2024-05-15 18:25:35 +01:00
Morgan 5d3c72271b chore: prop disableEditableHeading in availability settings (#15041) 2024-05-15 17:16:35 +03:00
Alex van Andel 775010cd2d chore: Allow disabling email sending without errors (#15051)
* chore: Allow disabling email sending without errors

* Explicitly return null for some deps down the tree
2024-05-15 15:06:44 +01:00
Udit Takkar 87a8b5a8dd fix: invalid type of enable_transcription_storage (#15049)
* fix: invalid type of enable_transcription_storage

* fix: type err
2024-05-15 16:12:24 +05:30
Morgan 973b624882 fix: disable useGetOAuthClientManagedUsers if clientId not defined (#15040) 2024-05-15 07:19:22 +00:00
Morgan 0a0c5ef61e chore: more fields event-types apiv2 (#15031) 2024-05-14 22:51:21 +01:00
Keith Williams 46f67bba9b chore: v4.0.9 (#15035)
release.yml / release (push) Canceled after 0s
2024-05-14 21:00:57 +00:00
Anik Dhabal Babu d9806da049 fix: better errors for platform (#14987)
## What does this PR do?

<!-- 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 #14977 

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Mandatory Tasks (DO NOT REMOVE)

- [ ] 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)
- [ ] 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)

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR

## Checklist

<!-- Remove bullet points below that don't apply to you -->

- 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
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my changes generate no new warnings
2024-05-14 11:56:44 -07:00
Alex van Andel 42fc49488d fix: Impersonation flicker on timezone change (#14738) 2024-05-14 11:56:25 -07:00
Omar LópezandCarina Wollendorfer e030fe0e07 fix: error when unlinking google account (#15011)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-14 10:10:41 -07:00
Udit Takkar 51428087ef chore: enable transcription for teams plan users (#15025) 2024-05-14 09:28:13 -04:00
Morgan 3942df369d chore: open disableGuests for event-types apiv2 (#15001)
* chore: open disableGuests for eventtypes apiv2

* fixup! chore: open disableGuests for eventtypes apiv2

* fixup! fixup! chore: open disableGuests for eventtypes apiv2

* chore: add disableGuests to create handler
2024-05-14 10:50:12 +00:00
Morgan 1d309a6f09 chore: hide oauthClientId from session email in booker (#15002) 2024-05-13 18:25:34 +00:00
Amit Sharma 56f31eee7a fix: Regression: Can’t set person to forward OOO bookings (#15003) 2024-05-13 10:29:56 -04:00
2dae0bfb8c fix: add retryCount to workflowReminder (#14943)
* add retry count to workflow reminder

* add logic to for retry count

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-13 13:25:22 +00:00
Hariom Balhara 917c7b0764 fix: Allow less than 30 seats for orgs (#14995)
* fix: Allow less than 30 seats for orgs

* Add test

* Stub env variables
2024-05-13 11:37:10 +00:00
fd64b0f468 fix: meeting ended and started webhooks trigger (#14864)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-10 12:49:16 -07:00
Syed Ali Shahbaz b1909337db fix: SAML fixes for uppercase email & GOOGLE → SAML idp switch (#14971)
* fix uppercase email slip

* fix google → SAML idp change
2024-05-10 15:09:45 +00:00
vikrant845andborawakevikrant 039bf6d4e3 fix: Fixed email embed for team when specific times are chosen embeds the wrong URL (#14975)
* Fixed team embed url

* Added comments

---------

Co-authored-by: borawakevikrant <borawakevikrant@gmail.com>
2024-05-10 14:05:09 +00:00
sean-brydonandUdit Takkar 83e15c4ce1 feat: orgs create dynamic events from org member list (#14912)
* implement basic UI - WIP

* Add copy + open in tab + center correctly

* use paragraph for link

* fix button spacing and domain takes up w-full

* Add fade in to bar instead of framer motion

* Move truncate to block level element instead of p

* chore:fix tablet styles

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-10 13:06:11 +00:00
Hariom Balhara 7d6ecf7201 Release new versions for embed (#14958) 2024-05-10 12:49:47 +00:00
Rajiv Sahal bc42c27230 chore: dashboard fixes (#14968)
* add rewrites for platform dashboard

* fix loading checking

* add checks

* dont display banner for platform client

* custom hooks for pllatform

* add comment for better explanation

* get data from custom hook

* update returned user attributes

* get data from custom hook
2024-05-10 15:16:03 +03:00
Peer Richelsen 57abb26c31 fixed giphy (#14973) 2024-05-10 12:02:10 +00:00
Hariom Balhara a136511c8a fix: Show error log (#14960) 2024-05-10 08:47:57 +01:00
Morgan c65dca9486 fix: api v2 hotfix google calendar, exception details, billing check guard (#14967) 2024-05-10 07:36:51 +00:00
Omar López 816d31918d chore: v4.0.8 (#14965)
release.yml / release (push) Canceled after 0s
2024-05-09 13:20:59 -07:00
Ayush MhetreandCarina Wollendorfer 097a69d7d7 fix: save workflow button not visible on dev env (#14910)
* fix: save workflow button not visible on dev env

* refactor: use the CALCOM_ENV constant

* fix: ui fix for header on workspace pages

* refactor: remove unused variable

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-09 17:46:39 +00:00
c2a07e221d feat: platform onboarding flow and dashboard (#14721)
* add endpoint to fetch managed user from client id

* update typings

* minor tweaks

* custom hook to fetch managed users from client id

* add translations for platform onboarding

* add isPlatformOrg boolean to figure out which is platform and which is not

* set isPlatform hook based on data obtained from org

* add limitWidth prop to control component width

* add props to shell to make sidebar display different tabs based on if the shell isPlattform or not

* platform related pages

* fix merge conflicts

* fix merge conflicts

* platform oauth client form and card

* remove everything related to platform from organization

* update oauth client card and form

* fixup

* fix imports and remove logs

* fixup

* update redirect url

* split oauth client form into separate update and create forms

* separate forms for create and edit oauth clients

* fixup

* fixup

* dynamic routes for oauth client edit page

* fixup fixup

* fix to not show error when redirect uri is empty

* refactor create handler for org

* cleaup comments

* add custom hook to check user billing

* export managed user type

* refactor platform index page

* refactor edit and create pages

* dashboard component containing oauth client list and managed user

* common oauth client form used for create and edit form

* platform pricing helper

* platform pricing component

* fix typing and data response for billing

* use custom hook to check team billing info

* fix type checks

* upgrade conditional rendering for upgrade to org banner

* add isLoading prop to check button loading state

* pass in button handler

* add custom hook to subscribe to stripe and typings

* update typings

* fix incorrect endpoint

* pass in team id as prop

* fix type check

* update stripe success and cancel redirect url

* add and pass redirect url param to custom hook

* custom hooks for platform

* cleanup

* update imports

* fix merge conflicts

* fixup

* fixup fixup

* fixup

* merge conflicts fixup

* merge conlficts battle :(

* minor fixes

* skip admin checks for a platform client

* fix typo

* append slug with _platform for a platform user

* PR feedback

* dashboard refactor

* bring back org form to its orginal state

* add platform folder to ee

* update typings

* use new create platform form

* fixup

* fix typo and update plans

* simplifying rendering

* remove managed users endpoint since it already exists

* url for endpoint

* rename tabs

* pr feedback

* managed users endpoint

* update endpoint

* remove form

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: exception <erik@erosemberg.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-05-09 13:01:10 -04:00
Morgan 6d8fbfc49a fix: gcal save get credentials from token.tokens (#14962) 2024-05-09 14:08:31 +00:00
Carina WollendorferandCarinaWolli 304ef29618 fix: buffer times in handleNewBooking (#14871)
* get buffer busy time from bookings outside of start and end time

* add test for buffers

* fix typo

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-05-09 13:39:32 +00:00
Lauris Skraucis b8890c841c fix: managed user timeZone, timeFormat and weekStart during creation (#14955)
* refactor: create, update user inputs use types instead of custom validators

* fix: save weekStart, timeFormat, timeZone when creating user

* fix: save weekStart, timeFormat, timeZone when creating user

* e2e: fix env variables and test weekStart, timeZone and timeFormat during user create
2024-05-09 10:25:14 -03:00
Amit SharmaandKeith Williams 2f5768b74f fix: Only able to create one webhook per event type (#14922)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-09 02:40:13 +00:00
Carina WollendorferandCarinaWolli c1416173e8 attempt to fix flaky e2e test (#14949)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-05-08 18:07:30 -04:00
Morgan 726af4970c fix: sync book event form values with bookingData (#14945)
* fix: sync book event form values with bookingData

* fixup! fix: sync book event form values with bookingData
2024-05-08 15:14:40 +00:00
Keith Williams d15b7d8168 chore: Update PR template to include more mandatory tasks (#14941)
* chore: Update PR template to include more mandatory tasks

* Add XXXX to GitHub number

* Update PULL_REQUEST_TEMPLATE.md
2024-05-08 14:37:17 +01:00
Alex van Andel c5bd45fdfb feat: Redirect when event type does not match booking type (#14460)
* feat: Redirect when event type does not match booking type

* Favour early return

* If multiple bookingUid/rescheduleUid given, use first

* rescheduleUid/bookingUid behaviour is required
2024-05-08 10:33:22 -03:00
Morgan 5d7b2a12a7 fix: redis cache key overlap (#14938) 2024-05-08 10:41:34 +00:00
Morgan 7c90d1ed76 feat: handleCreateBooking for platform booker (#14939) 2024-05-08 16:11:01 +05:30
Peer Richelsen 5ca10d1e83 feat: Added admin-api page (#14890)
* added admin-api page

* i18n
2024-05-08 10:15:20 +00:00
sean-brydon f0e59c2331 fix: pass query params to dynamic meetings (#14920) 2024-05-08 09:53:07 +00:00
Alex van AndelandKeith Williams f812d92045 chore: Release/v4.0.7 (#14933)
release.yml / release (push) Canceled after 0s
* chore: release v4.0.7

* fix: Remove lockfile, could be broken

* fix: Bump lockfile (again)

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-08 00:17:51 +01:00
Crowdin Bot 23a17c4e8a New Crowdin translations by Github Action 2024-05-07 22:24:57 +00:00
Keith Williams 5beb039003 fix: Caching key for CI database (#14932)
* fix: Caching key for db

* Fix line length
2024-05-07 22:21:39 +00:00
Keith Williams 63dd357664 chore: Removed duplicate production build (#14836)
* chore: Removed duplicate production build

* fix references

* Move env vars to top level of e2e

* job renaming

* Added env vars to top level of all e2e jobs

* Removed part of cache key that causes issues and is moot

* Using buildjet hardware so the caching works

* clean up
2024-05-07 12:56:19 -07:00
b6a57381c7 feat: redirect after booking: option to forward parameters (#14235)
* feat: redirect after booking: option to forward parameters

* fix: unit test

* fix: stripe passing its own params

* fix: manually add stripe params

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-07 12:15:20 -04:00
Alex van Andel 7cf840723b Async scalars & less queries due to grouping (#14904) 2024-05-07 15:18:07 +01:00
sean-brydonandUdit Takkar bc0c40d260 feat: cal-3452 parameter for impersonation (#14605)
* feat: impersonate trigger when there is a URL param

* feat: update intercom sidebar

* fix: add to onOpen too

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-07 10:33:19 -03:00
MorganandKeith Williams 89612a7a68 fix: add prisma client generated types to atoms package (#14916)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-07 12:59:41 +00:00
a2620a0e50 feat: atoms team booking (#14525)
* feat: setup usePublicEvent with orgSlug and duration

* feat: setup useAvailableSlots with eventTypeSlug and orgSlug

* fix & feat: fix TS issues and allow passing multiple users to Booker

* refactor: dont show [15min] [30min] [60min] [1h30min] picker

* fix: pass orgSlug to handleNewBooking -> loadUsers -> findUsersByUsername to not return empty users[]

* refactor: display attendees in booking confirmation screen

* fix: getting slots get orgSlug from props not event

* revert: Booker username use props instead of hardcoded values

* refactor: setup BookerStore org and use it for selectedTime + isDynamic logic

* refactor: hide 'what is this meeting about' input in final booking step

* fix: TS error

* revert: what is the meeting about field hiding

* revert: make org as org? in booker store

* revert: createNewBooking -> loadUsers -> get forced orgSlug from header not body

* refactor: useHandleBookEvent get orgSlug from booker store not props

* fix: if entity undefined dont access orgSlug

* refactor: add isDynamic prop as queryKey for usePublicEvent

* refactor: remove unused prop

* re-add docs

* fix: typescript error

* fix: force platform orgSlug in handleNewBooking.ts

* fix: remove duplicate setSelectedDuration declaration

* refactor: destructure properies from body instead of req.body

* fix: entity optional and hide event members

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-05-07 12:51:00 +00:00
sean-brydon cfcf5b168c fix: slot check (#14917) 2024-05-07 12:43:10 +00:00
3f4d9962a7 fix: reschedule with google meet (#14730)
* fix: reschedule with google meet

* fix: type err

* test: add google meet test

* chore: save progress

* test: add individual event type

* chore: add one more check

* fix: type err

* chore: use updatedEvent before createdEvent

* refactor: update event

---------

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-07 18:09:12 +05:30
Keith Williams 83917e8655 chore: Add build of API v2 to PR checks (#14902)
* chore: Add build of API v2 to PR checks

* Actually added the v2 build to the pipeline...

* Added back the original yarn install

* Updated the main PR flow to require API builds before tests
2024-05-07 09:05:49 -03:00
Carina WollendorferandCarinaWolli f4bd0dbcc9 remove global variable (#14907)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-05-07 11:04:27 +01:00
c87b8b43f9 Add resourceOwner.id related test (#14816)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-06 23:50:59 +00:00
shaik-zeeshanandJoe Au-Yeung b2baaea84c feat: "Add an Override" calendar starts with the day specified in general settings (#14663)
* feat: Add an Override calendar starts with the day specified in general settings

* fix: types not matching

* fix: update edit calendar to use user's weekStart

* fix: weekstart defaults to 0 when not provided

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-05-06 19:00:03 -03:00
Vikas PatilandJoe Au-Yeung 0e431ba7c6 feat: posthog app added (#14801)
* feat: posthog app added

* fix:requested changes

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-05-06 15:54:53 -03:00
sean-brydon b62f6eee3c fix: date override modal (#14854) 2024-05-06 18:34:17 +00:00
Peer Richelsen 2d0cac0dad fix: windows app styling (#14805) 2024-05-06 17:41:45 +00:00
Morgan 0c3d1b3034 chore: bump atoms v1.0.40 (#14901) 2024-05-06 17:31:08 +00:00
74f6d7686b fix: zod error message and missing translation (#14787)
* fix: issue#14785 and issue#14786

* fix: some changes

* fix

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-06 16:03:24 +00:00
Amit SharmaandKeith Williams 49601bed6f fix: Sender ID info not being shown on 'info' hover in 'add action' flow (#14879)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-06 12:16:03 -03:00
ErikandMorgan b9659051d4 chore: Cache cityTimezones in Platform (#14823)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-05-06 12:00:36 -03:00
Peer RichelsenandKeith Williams 9c96507af2 chore: centered cal video logo (#14896)
* centered cal video logo

* Update apps/web/modules/videos/views/videos-single-view.tsx

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-05-06 14:25:14 +00:00
Utkersh Rajvenshi 2352babe93 fix: UI breaking in AddActionDialog on reducing browser window height (#14892) 2024-05-06 14:24:45 +00:00
Keith Williams 1467815c3f chore: Use 4vCPU BuildJet hardware for type checks (#14900) 2024-05-06 10:31:37 -03:00
7254cd97e2 chore: Use better buildjet hardware for e2e (#14835)
* chore: Use better buildjet server for e2e

* Backed down buildjet hardware where not needed

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-06 12:27:14 +00:00
Morgan 4cb77abfd7 fix: api v2 docs in prod (#14898) 2024-05-06 11:50:21 +00:00
8deedfd361 fix: Booking Cancelled Webhook - Organiser webhook payload is missing id. #14697 (#14745)
* fix: include id information to be consistent with create/reschedule flows

* fix: include username as well

* function expectBookingCancelledWebhookToHaveBeenFired created

* chore: undo yarn.lock

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-05-06 08:14:09 +00:00
3a0c766ed5 fix: Support adding a non-existent email as the owner of the organization (#14569)
* fix: Support adding an non-existent email as the owner of the organization

* Remove dead code

* fixes

* add test

* self review changes

* Update packages/trpc/server/routers/viewer/organizations/create.handler.ts

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>

* Fix typo everywhere

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-05-06 07:09:22 +00:00
Hariom Balhara 4f57e3201d test: Google CalendarService test (#14793) 2024-05-06 12:33:16 +05:30
MOHD RAZA e404f7eb88 UI breaking in AddActionDialog fixed (#14883) 2024-05-06 07:02:43 +00:00
5c2b561a5c fix webhook e2e test (#14850)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-05-03 14:59:52 +00:00
Amit Sharma 57b404f721 feat: Add reschedule information with booking Read (#14853) 2024-05-03 14:31:04 +00:00
Amit SharmaandCarina Wollendorfer df9a6621cf fix: Sender ID info not shown on hover (#14846)
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2024-05-03 14:08:09 +00:00
c478f73327 feat: orgs trigger alert when loading a calendar and no availability is (#14796)
* Install upstash redis again - base of noficationSender

* Setup email handler etc and use tasker

* Remove logs

* Sending emails and correct format and spacing in emails

* Update email styles

* Update email styles

* add switch to enable feature

* fix: reset tasker types - will fix in new PR

* WIP: test suite

* WIP: test suite

* Add redis service and add WIP mock test

* Add tests for redis service and fix lpush

* More working tests

* More working tests

* fix: type error + typo

* update export to match i18n next mock

* (debug) push for debug

* Fix: fixed hosting in mocks

* Add common.json i18n

* add better test descriptions

* reset mocks after each test to allow concurancy

* Remove redundant RedisService.test.ts

* Rename and remove redundant isAdmin check

* Rename to .d.ts

* Add key versioning to constructRedisKey

* Update packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* Update packages/trpc/server/routers/viewer/slots/handleNotificationWhenNoSlots.ts

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* Add try/catch

* fix breaking when option === undefined

* Add missing await to Promise.all

* Rename data stored in redis to save space

* Include thrown error in logs

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-05-03 13:48:04 +00:00
Hariom Balhara 88f4dd2446 Ensure that availability is fetched in parallel (#14809) 2024-05-03 13:56:10 +01:00
Keith Williams 97b2816897 v4.0.6 (#14865)
release.yml / release (push) Canceled after 0s
2024-05-02 22:23:46 +00:00
Erik 00372b6b3b chore: Discard unused Platform stripe events (#14822)
* chore: discard unused webhooks

* chore: discard early
2024-05-02 16:31:50 +00:00
Hariom Balhara 6c8fced247 fix: Snippet path in preview (#14830)
* fix: Snippet path in preview

* Add test
2024-05-02 16:03:57 +00:00
Erik 52813b01e6 chore: don't try to increase usage for legacy plans (#14856) 2024-05-02 15:24:42 +00:00
Erik 77a98d96c8 chore: attach unique request ids (#14857) 2024-05-02 15:24:24 +00:00
Peer RichelsenandUdit Takkar 676eac357f chore: fixed enterprise warning and description (#14791)
* fixed enterprise warning and description

* chore: remove fullstop

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-05-02 15:14:13 +00:00
Joe Au-Yeung 5bafd1b264 chore: Allow disabling attendee emails if SMS_ATTENDEE is a part of the workflow (#14806)
* Allow disabling confirmation emails to attendees if workflow contains SMS

* Update copy
2024-05-02 10:44:08 -04:00
0391bfe887 test: add unit tests for sms sending (#14737)
* add tests for workflow sms

* fix type error

* improvements

* add tests for team workflow

* rename test

* add emailsToReceive everywhere

* fix type errors

* code clean up

* fix fresh-booking.test.ts

* add destination email to emailsToReceive

* remove unused webhooks

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-05-02 10:42:38 -04:00
Peer Richelsen c18407d75a full screen ratings (#14839) 2024-05-02 09:43:28 +00:00
Erik a631286770 chore: Add default to match migration for Platform Billing (#14847) 2024-05-02 05:29:32 +00:00
Carina WollendorferandCarinaWolli dd78741158 fix e2e test (#14845)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-05-01 20:45:40 +00:00
Peer Richelsen 8f69811e25 chore: Update README.md (#14819) 2024-05-01 21:18:53 +02:00
Erik 3a63a8b228 chore: Bring back next-18next.config.js (#14841)
* chore: bring back next-18next.config.js

* chore: bring back next-18next.config.js
2024-05-01 10:23:03 -03:00
Somay ChauhanandPeer Richelsen ef98105503 feat: send organization_id to intercom (#14811)
* send organization_id to intercom

* send org slug instead of org id

* fix: type errors

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-04-30 21:38:18 +00:00
Erik a18f1eb382 v4.0.5 (#14820)
release.yml / release (push) Canceled after 0s
2024-04-30 21:23:51 +00:00
Joe Au-Yeung 2ab6315a8e Add two more shards to E2E pipeline (#14818) 2024-04-30 20:31:17 +00:00
Joe Au-Yeungandexception 258ebe0420 chore: Update yarn.lock file (#14812)
* Update yarn.lock file

* chore: update lockfile

---------

Co-authored-by: exception <erik@erosemberg.com>
2024-04-30 20:05:29 +00:00
Erik 1cfd2f7053 feat: Billing Service (#14756)
* chore: Billing Service

* chore: others

* chore: don't type

* chore: typo

* chore: use proper wh secret

* chore: updates

* chore: lockfile

* chore: docs and comment out

* chore: updates and include plan

* chore: config

* chore: bring back alias

* chore: update lockfile

* chore: refactor

* chore: default none plan in db

* chore: read from env vars

* chore: feedback

* chore: remove json config

* chore: proper type
2024-04-30 20:02:04 +00:00
Hariom Balhara a51024e915 hotfix-conditional-valid-resource-owner-check (#14814) 2024-04-30 15:05:00 +00:00
Hariom BalharaandJoe Au-Yeung 900e90cd2c fix: With Credential Sync enabled, fix expired google calendar token handling - Refresh wasn't working (#14788)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-04-29 20:37:18 +00:00
Somay Chauhan 46ffeaee82 feat: add additional attributes to intercom (#14794)
* feat: add additional attributes to intercom

* remove console.log

* added is_premium
2024-04-29 19:50:07 +02:00
Alex van Andel 22425bb693 Revert "fix: invalid Cancelled / Rescheduled downstream calendar block (#14700)" (#14800)
This reverts commit 0306ad737f.
2024-04-29 13:31:02 -03:00
Syed Ali Shahbaz ad0b8a668c fix: Unbork Team POST API (#14803)
* Add newly added mandatory fields to partial

* --WIP
2024-04-29 17:10:10 +01:00
bbad2c9d31 fix: v2 ts config and errors (#14799)
* fix: exclude platform examples

* fix: v2 event-types TS errors

* fix: nest entry file

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-04-29 16:06:25 +00:00
Amit SharmaandUdit Takkar 8b28a6f5df feat: when creating workflow from within event-type, preselect it (#14774)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-29 14:23:28 +00:00
Chris 93bb37702c feat: Add app store entry for Clic (CheckIn application) (#14754)
* feat: create app

* feat: create app

* feat: initial commit
2024-04-29 13:26:13 +00:00
Alex van Andel 4a8f251d89 fix: #14769 (#14777) 2024-04-27 18:14:37 +02:00
lambertrocherandlambert cf44919cdd fix missing french translation for keys round_robin_emailed_you_and_attendees and round_robin_emailed_you_and_attendees_recurring (#14773)
Co-authored-by: lambert <lambert.rocher@jellysmack.com>
2024-04-27 01:19:56 +00:00
Morgan 024e40b26b chore: api v2 tsconfig manual path to internal packages (#14771) 2024-04-26 20:40:30 -03:00
Nishan SinghandKeith Williams 7c354321be feat: Add linting rule to recursively check for Prisma includes using… (#13652)
* feat: Add linting rule to recursively check for Prisma includes using only 'true'

* Fixing linting errors

* More linting fixes

* Disabled linting for forms

* Update recommended.ts

* Removed ignores

* Fixed warning

* Type fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-04-26 21:16:16 +00:00
Abhiuday Gupta fc16ec59d3 fix: confirm booking doesn't respect minimum booking notice (#14577)
Signed-off-by: Abhiuday <contact.abhiuday@gmail.com>
2024-04-26 11:58:11 -04:00
Hariom Balhara ab26481738 fix child element click popup (#14749) 2024-04-26 14:37:27 +00:00
Amit SharmaandUdit Takkar 2fbc06314e feat: Hide recurring tab when payments are enabled (#14731)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-26 10:18:29 -04:00
sean-brydonandUdit Takkar 386006fad0 feat: date range picker (#14713)
* feat: add DateRangePicker.tsx and Calendar.tsx to match DS

* chore:cleanup

* tests: remove as these are duplicate tests from the library themselves

* fix: mobile sizes

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-26 13:38:42 +00:00
ceb555460e feat: Platform Rate limiting + access token caching (#14560)
* feat: Platform Rate limiting + access token caching

* chore: changes

* chore: cleanup

* chore: remove yarnlock changes

* chore: yarnlcok

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-04-26 13:14:13 +00:00
Lauris Skraucis c28870e029 refactor: booker atom unmount reset duration query param (#14766) 2024-04-26 14:01:50 +03:00
Syed Ali Shahbaz 0650898302 feat: Add ratings to insights (#14687) 2024-04-26 11:58:06 +01:00
Lauris Skraucis d004815a68 feat: v2 managed user create default schedule given timeZone (#14727)
* schedules service: function to create default user schedule

* managed user: create default schedule if timeZone provided

* swagger update
2024-04-26 09:20:29 +00:00
Morgan 5d76bd6b9c v4.0.4 (#14764)
release.yml / release (push) Canceled after 0s
2024-04-26 09:21:07 +03:00
Erik d02947f887 chore: SDK cleanup (#14763) 2024-04-25 23:22:47 +00:00
Erik a3a95a1496 chore: SDK readme (#14761) 2024-04-25 20:26:46 +00:00
Hariom BalharaandJoe Au-Yeung d47c6b3fdb fix: Credential Syncing Improvements (#14588)
* Add example app to test credential sync

* Fixes

* Changes to normalize flow of GoogleCalendar and Zoom

* Add unit tests

* PR Feedback

* credential-sync-more-tests-and-more-apps

* Fix yarn.lock

* Clear cache

* Add test

* Fix yarn.lock

* Fix 204 handling

* Fix yarn.lock

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-04-25 12:47:17 -04:00
Morgan ebbb1dcce3 fix: oAuthClient Creation Form (#14750) 2024-04-25 13:51:03 +00:00
Morgan 8fbb1ed426 chore: bump atoms 1.0.35 and wrap context (#14744) 2024-04-25 11:47:30 +03:00
Omar López d8adc8eeb1 fix: Check permissions for team OOO entries (#14736) 2024-04-24 19:24:59 +00:00
Lauris Skraucis a7bd4aee85 fix: booker atom month reset (#14732) 2024-04-24 14:36:29 +00:00
Lauris SkraucisandMorgan 82fcda4b42 fix: booker atom reschedule data (#14714)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-04-24 13:54:05 +03:00
Lauris SkraucisandMorgan b3b7a09cfb fix: booker atom not resetting month query param (#14715)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-04-24 07:35:33 +00:00
Omar López 60ae817f30 chore: v4.0.3 (#14725)
release.yml / release (push) Canceled after 0s
Bump version
2024-04-23 22:53:24 +00:00
Udit Takkar 74166fc2cb fix: settings layout for org admin (#14723) 2024-04-23 21:53:17 +00:00
Patrick Göler von Ravensburg fb997045f2 feat: Add app store entry for "Autocheckin" (Challenge/Bounty) (#14683) 2024-04-23 21:52:03 +00:00
Keith Williams 73a79cf29b fix: Improve French translations (#14718) 2024-04-23 17:35:56 -04:00
Omar López 54a1663a8b v4.0.2 (#14724) 2024-04-23 21:16:14 +00:00
883d745784 feat: SMS/Whatsapp to attendee for teams (#14648)
* remove event type requires confirmation

* allow sms sending also for team (frontend)

* allow text to attendee for team (backend)

* add rate limiting for SMS (WIP)

* add functionality to lock and unlock SMS sending (+admin UI view)

* rename to smsLockState

* add ability to lock users and teams

* don't send sms if locked

* add missing imports

* fix rate limit namespace

* add translation

* fix type error

* remove prop from UsersTable

* add smsLockState to buildUser

* code clean up

* create seperate sms rate limit function

* fix type error

* add missing userId and teamId to sendSMS

* code improvements

* add User Team type

* fix marking as reviewed

* check monthly sms limit only for user

* don't lock orgs

* fix type error

* fix avatars

* fix type error

* fix type error

* fix type error

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-23 19:48:44 +00:00
571e93a19a chore: EventManager add cancelEvent method (#14261)
* fix timezone display on booking page to reflect event availability timezone

* migrate fetching event owner's schedule to server side

* migrate fetching event owner's schedule to server side

* fix e2e test errors

* Add WEBAPP_URL_FOR_OAUTH to salesforce auth

* In event manager constructor include "_crm" credentials as calendar creds

* Change crm apps to type to end with `_crm`

* Move sendgrid out of CRM

* Add zoho bigin to CRM apps

* When getting apps, use slug

* Add `crm` variants

* Hubspot Oauth use `WEBAPP_URL_FOR_OAUTH`

* Refactor creating credentials

* Fix empty CRM page

* Use credentials with `_crm`

* Abstract getAppCategoryTitle

* Add integration.handler changes

* When searching for credential, look for current credentials in class

* On cancel, delete 3rd party events

* Fix tests

* Type fix

* Type fixes

* Remove apiDeletes

* Type fixes

* Specific typing

---------

Co-authored-by: Shaik-Sirajuddin <sirajuddinshaik30gmail.com>
Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: Shaik-Sirajuddin <89742297+Shaik-Sirajuddin@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2024-04-23 17:54:45 +00:00
Peer RichelsenandUdit Takkar d16428da6c changed translation (#14710)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-23 16:14:12 +00:00
sean-brydon 8c5438663c feat: improve error handling for signup events (#14668)
* feat: improve error handling for signup events

* chore: fix typo
2024-04-23 16:13:10 +00:00
Hariom Balhara ed76d163cf fix: Add navigatedToBooker event and fix infinite loop of events (#14694)
## What does this PR do?

1. cal.com/sales page is a special case where embed-iframe exists in top as well as in iframe. This causes a looping of events somehow due to embed-iframe firing events and those being received by the top embed-iframe.
2. Added new `navigatedToBooker` event to know when the user lands on booker page.


Corresponding PR in website that uses `navigatedToBooker` event  and has a demo as well https://github.com/calcom/website/pull/741

Earlier added `routed` event seems to happen too early

## Type of change
- Bug fix (non-breaking change which fixes an issue)

## How should this be tested?
- Simply navigate to booker and wait for the event.

## Mandatory Tasks
- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

## Tests
- Didn't add any tests for this as the changed seems straightforward

## Checklist
- I haven't added tests that prove my fix is effective or that my feature works
2024-04-23 10:36:12 +01:00
9215725dad fix: browser back button not working (#13345)
* fixed the browser back button not woking

* fix back button

* added shouldReplace param to query-params helper.

* revert yarn.lock

* fix: test

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-04-22 18:42:54 -04:00
649 changed files with 19514 additions and 4719 deletions
+1
View File
@@ -299,6 +299,7 @@ E2E_TEST_CALCOM_GCAL_KEYS=
CALCOM_CREDENTIAL_SYNC_SECRET=""
# 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"
# This the endpoint from which the token is fetched
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
# Key should match on Cal.com and your application
# must be 24 bytes for AES256 encryption algorithm
+6 -24
View File
@@ -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. -->
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
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. -->
- If there is a requirement document, please, share it here.
- 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
- [ ] 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)
- [ ] 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)
## How should this be tested?
@@ -35,10 +24,6 @@ Fixes # (issue)
- What is expected (happy path) to have (input and output)?
- 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
<!-- 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)
- 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 checked if my PR needs changes to the documentation
- 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
+5 -2
View File
@@ -8,14 +8,17 @@ runs:
using: "composite"
steps:
- name: Cache production build
uses: buildjet/cache@v3
uses: buildjet/cache@v4
id: cache-build
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
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-3: ${{ github.event.pull_request.number || github.ref }}
# Ensures production-build.yml will always be fresh
key-4: ${{ github.sha }}
with:
path: |
+1 -1
View File
@@ -12,7 +12,7 @@ runs:
steps:
- name: Cache database
id: cache-db
uses: buildjet/cache@v3
uses: buildjet/cache@v4
env:
cache-name: cache-db
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
@@ -4,7 +4,7 @@ runs:
using: "composite"
steps:
- name: Checkout repo
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 2
+4 -4
View File
@@ -20,7 +20,7 @@ runs:
using: "composite"
steps:
- name: Use Node ${{ inputs.node_version }}
uses: buildjet/setup-node@v3
uses: buildjet/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
- 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 cache is also reusable between arch and os.
- name: Restore yarn cache
uses: buildjet/cache@v3
uses: buildjet/cache@v4
id: yarn-download-cache
with:
path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }}
@@ -43,7 +43,7 @@ runs:
# Invalidated on yarn.lock changes
- name: Restore node_modules
id: yarn-nm-cache
uses: buildjet/cache@v3
uses: buildjet/cache@v4
with:
path: "**/node_modules/"
key: ${{ runner.os }}-yarn-nm-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
@@ -51,7 +51,7 @@ runs:
# Invalidated on yarn.lock changes
- name: Restore yarn install state
id: yarn-install-state-cache
uses: buildjet/cache@v3
uses: buildjet/cache@v4
with:
path: .yarn/ci-cache/
key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }}
@@ -5,7 +5,7 @@ runs:
steps:
- name: Cache playwright binaries
id: playwright-cache
uses: buildjet/cache@v3
uses: buildjet/cache@v4
with:
path: |
~/Library/Caches/ms-playwright
+7 -3
View File
@@ -1,6 +1,10 @@
"❗️ migrations":
- packages/prisma/migrations/**/migration.sql
- changed-files:
- any-glob-to-any-file:
- packages/prisma/migrations/**/migration.sql
"❗️ .env changes":
- .env.example
- .env.appStore.example
- changed-files:
- any-glob-to-any-file:
- .env.example
- .env.appStore.example
+27 -7
View File
@@ -1,4 +1,4 @@
name: Production Build
name: Production Builds
on:
workflow_call:
@@ -18,7 +18,10 @@ env:
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 }}
@@ -33,13 +36,10 @@ env:
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
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:
build:
name: Build API V1
name: Build API v1
runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 30
services:
@@ -60,11 +60,31 @@ jobs:
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/cache-db
- 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"
yarn turbo run build --filter=@calcom/api...
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
+3 -3
View File
@@ -6,10 +6,10 @@ on:
jobs:
cleanup:
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Cleanup
run: |
@@ -21,7 +21,7 @@ jobs:
echo "Fetching list of cache key"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
@@ -22,7 +22,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }} # So we can test on forks
fetch-depth: 0
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
check-types:
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- name: Show info
@@ -18,9 +18,9 @@ jobs:
run: |
LAST_DAY=$(date -d tomorrow +%d)
if [ "$LAST_DAY" == "01" ]; then
echo "::set-output name=is_last_day::true"
echo "is_last_day=true" >> "$GITHUB_OUTPUT"
else
echo "::set-output name=is_last_day::false"
echo "is_last_day=false" >> "$GITHUB_OUTPUT"
fi
- name: cURL request
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_ACCESS_TOKEN }}
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- uses: buildjet/cache-delete@v1
with:
cache_key: ${{ inputs.cache_key }}
+37 -36
View File
@@ -1,8 +1,43 @@
name: E2E App-Store Apps Tests
name: E2E App Store Tests
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:
e2e-app-store:
timeout-minutes: 20
@@ -44,7 +79,7 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
@@ -59,40 +94,6 @@ jobs:
- uses: ./.github/actions/cache-build
- name: Run Tests
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 }}
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_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 }}
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v4
+36 -30
View File
@@ -3,6 +3,41 @@ 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:
e2e-embed:
timeout-minutes: 20
@@ -36,7 +71,7 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
@@ -46,35 +81,6 @@ jobs:
run: |
yarn e2e:embed-react --shard=${{ matrix.shard }}/${{ strategy.job-total }}
yarn workspace @calcom/embed-react packaged:tests
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 }}
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 }}
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v4
+36 -31
View File
@@ -3,6 +3,41 @@ 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:
e2e-embed:
timeout-minutes: 20
@@ -44,7 +79,7 @@ jobs:
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
@@ -52,36 +87,6 @@ jobs:
- uses: ./.github/actions/cache-build
- name: Run Tests
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 }}
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 }}
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 }}
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v4
+39 -38
View File
@@ -1,12 +1,47 @@
name: E2E tests
name: E2E
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:
e2e:
timeout-minutes: 20
name: E2E tests (${{ matrix.shard }}/${{ strategy.job-total }})
name: E2E (${{ matrix.shard }}/${{ strategy.job-total }})
runs-on: buildjet-4vcpu-ubuntu-2204
services:
postgres:
@@ -36,13 +71,13 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
shard: [1, 2, 3, 4, 5, 6, 7]
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
@@ -50,40 +85,6 @@ jobs:
- uses: ./.github/actions/cache-build
- name: Run Tests
run: yarn e2e --shard=${{ matrix.shard }}/${{ strategy.job-total }}
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_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 }}
NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED: ${{ vars.CI_NEXT_PUBLIC_ORG_SELF_SERVE_ENABLED }}
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v4
@@ -1,25 +1,29 @@
name: Production Build
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_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 }}
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 }}
@@ -34,16 +38,11 @@ env:
SENDGRID_EMAIL: ${{ secrets.CI_SENDGRID_EMAIL }}
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
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:
build:
name: Build
runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 30
integration:
timeout-minutes: 20
name: Integration
runs-on: buildjet-8vcpu-ubuntu-2204
services:
postgres:
image: postgres:13
@@ -61,9 +60,30 @@ jobs:
--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
- uses: ./.github/actions/cache-build
- 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
+3 -5
View File
@@ -12,18 +12,16 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
sync-labels: ""
team-labels:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: equitybee/team-label-action@main
with:
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
@@ -39,7 +37,7 @@ jobs:
steps:
- name: Apply labels from linked issue to PR
uses: actions/github-script@v5
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
+1 -1
View File
@@ -6,7 +6,7 @@ jobs:
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- name: Run Linting with Reports
+41 -4
View File
@@ -7,18 +7,55 @@ on:
branches:
- 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:
build:
name: Production build
name: Production builds
if: ${{ github.event_name == 'push' }}
uses: ./.github/workflows/production-build.yml
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
analyze:
needs: build
if: always()
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/cache-build
+32 -25
View File
@@ -14,15 +14,15 @@ concurrency:
jobs:
changes:
name: Detect changes
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
permissions:
pull-requests: read
outputs:
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: dorny/paths-filter@v2
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -35,8 +35,8 @@ jobs:
uses: ./.github/workflows/check-types.yml
secrets: inherit
test:
name: Unit tests
unit-test:
name: Tests
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/unit-tests.yml
@@ -49,51 +49,58 @@ jobs:
uses: ./.github/workflows/lint.yml
secrets: inherit
build:
name: Production build
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/production-build.yml
secrets: inherit
build-api-v1:
name: Production build
name: Production builds
needs: [changes]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/api-v1-production-build.yml
secrets: inherit
build-without-database:
name: Production build (without database)
build-api-v2:
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]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/production-build-without-database.yml
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:
name: E2E tests
needs: [changes, lint, build]
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/e2e.yml
secrets: inherit
e2e-app-store:
name: E2E App Store tests
needs: [changes, lint, build]
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/e2e-app-store.yml
secrets: inherit
e2e-embed:
name: E2E embeds tests
needs: [changes, lint, build]
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/e2e-embed.yml
secrets: inherit
e2e-embed-react:
name: E2E React embeds tests
needs: [changes, lint, build]
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/e2e-embed-react.yml
secrets: inherit
@@ -106,9 +113,9 @@ jobs:
secrets: inherit
required:
needs: [changes, lint, type-check, test, build, build-api-v1, 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()
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- 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'))
+6 -11
View File
@@ -14,9 +14,9 @@ jobs:
embed: ${{ steps.filter.outputs.embed }}
embed-react: ${{ steps.filter.outputs.embed-react }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: dorny/paths-filter@v2
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -39,8 +39,8 @@ jobs:
secrets: inherit
build:
name: Production build
uses: ./.github/workflows/production-build.yml
name: Production builds
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
e2e:
@@ -67,15 +67,10 @@ jobs:
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
build-without-database:
name: Production build (without database)
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
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()
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- name: fail if conditional jobs failed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled')
@@ -1,5 +1,4 @@
name: Production Build (without database)
name: Production Builds
on:
workflow_call:
@@ -37,11 +36,11 @@ env:
jobs:
build:
name: Build
runs-on: ubuntu-latest
name: Web App
runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/cache-build
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: "Determine tag"
run: 'echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV'
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
ref: main # Always checkout main even for tagged releases
fetch-depth: 0
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
token: ${{ secrets.GH_ACCESS_TOKEN }}
+4 -3
View File
@@ -1,4 +1,4 @@
name: Unit tests
name: Unit
on:
workflow_call:
workflow_run:
@@ -6,10 +6,11 @@ on:
types: [completed]
jobs:
test:
name: Unit
timeout-minutes: 20
runs-on: buildjet-4vcpu-ubuntu-2204
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- 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
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 10
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
+2 -2
View File
@@ -24,7 +24,7 @@
<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://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://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>
@@ -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://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" />
<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://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>
+6 -2
View File
@@ -6,6 +6,7 @@ import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
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.
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" });
// save the user id in the request for later use
req.userId = apiKey.userId;
// save the isAdmin boolean here for later use
req.isAdmin = await isAdminGuard(req);
const { isAdmin, scope } = await isAdminGuard(req);
req.isSystemWideAdmin = isAdmin && scope === ScopeOfAdmin.SystemWide;
req.isOrganizationOwnerOrAdmin = isAdmin && scope === ScopeOfAdmin.OrgOwnerOrAdmin;
await next();
};
@@ -3,9 +3,9 @@ import type { NextMiddleware } from "next-api-middleware";
import { APP_CREDENTIAL_SHARING_ENABLED } from "@calcom/lib/constants";
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" });
}
@@ -4,9 +4,9 @@ import { HttpError } from "@calcom/lib/http-error";
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 */
if (!isAdmin) {
if (!isSystemWideAdmin) {
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
}
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
+30 -3
View File
@@ -1,10 +1,37 @@
import type { NextApiRequest } from "next";
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) => {
const { userId } = req;
const user = await prisma.user.findUnique({ where: { id: userId } });
return user?.role === UserPermissionRole.ADMIN;
const user = await prisma.user.findUnique({ where: { id: userId }, select: { role: true } });
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);
};
+4
View File
@@ -0,0 +1,4 @@
export const ScopeOfAdmin = {
SystemWide: "SystemWide",
OrgOwnerOrAdmin: "OrgOwnerOrAdmin",
} as const;
+1
View File
@@ -82,4 +82,5 @@ export const schemaBookingReadPublic = Booking.extend({
metadata: true,
status: true,
responses: true,
fromReschedule: true,
});
+3
View File
@@ -6,6 +6,9 @@ export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true })
hideBranding: true,
metadata: true,
pendingPayment: true,
isOrganization: true,
isPlatform: true,
smsLockState: true,
});
const schemaTeamRequiredParams = z.object({
+2 -1
View File
@@ -11,7 +11,8 @@ export declare module "next" {
method: string;
// session: { user: { id: number } };
// query: Partial<{ [key: string]: string | string[] }>;
isAdmin: boolean;
isSystemWideAdmin: boolean;
isOrganizationOwnerOrAdmin: boolean;
pagination: { take: number; skip: number };
}
}
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdAsString } from "~/lib/validations/shared/queryIdString";
export async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdAsString.parse(req.query);
// Admin can check any api key
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Check if user can access the api key
const apiKey = await prisma.apiKey.findFirst({
where: { id, userId },
+4 -4
View File
@@ -16,8 +16,8 @@ type CustomNextApiRequest = NextApiRequest & {
function handleAdminRequests(req: CustomNextApiRequest) {
// To match type safety with runtime
if (!hasReqArgs(req)) throw Error("Missing req.args");
const { userId, isAdmin } = req;
if (isAdmin && req.query.userId) {
const { userId, isSystemWideAdmin } = req;
if (isSystemWideAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
req.args.where = { userId: { in: userIds } };
@@ -30,8 +30,8 @@ function hasReqArgs(req: CustomNextApiRequest): req is Ensure<CustomNextApiReque
}
async function getHandler(req: CustomNextApiRequest) {
const { userId, isAdmin } = req;
req.args = isAdmin ? {} : { where: { userId } };
const { userId, isSystemWideAdmin } = req;
req.args = isSystemWideAdmin ? {} : { where: { userId } };
// Proof of concept: allowing mutation in exchange of composability
handleAdminRequests(req);
const data = await prisma.apiKey.findMany(req.args);
+4 -3
View File
@@ -10,7 +10,7 @@ import prisma from "@calcom/prisma";
import { apiKeyCreateBodySchema, apiKeyPublicSchema } from "~/lib/validations/api-key";
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { neverExpires, userId: bodyUserId, ...input } = apiKeyCreateBodySchema.parse(req.body);
const [hashedKey, apiKey] = generateUniqueAPIKey();
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 };
await prisma.user.findFirstOrThrow({ where });
args.data.userId = bodyUserId;
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
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.
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Find all user bookings, including attendees
const attendee = await prisma.attendee.findFirst({
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>) {
const { isAdmin } = req;
if (isAdmin) return;
const { isSystemWideAdmin } = req;
if (isSystemWideAdmin) return;
const { userId } = req;
const { bookingId } = body;
if (bookingId) {
+2 -2
View File
@@ -31,8 +31,8 @@ import { schemaAttendeeReadPublic } from "~/lib/validations/attendee";
* description: No attendees were found
*/
async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const args: Prisma.AttendeeFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } };
const { userId, isSystemWideAdmin } = req;
const args: Prisma.AttendeeFindManyArgs = isSystemWideAdmin ? {} : { where: { booking: { userId } } };
const data = await prisma.attendee.findMany(args);
const attendees = data.map((attendee) => schemaAttendeeReadPublic.parse(attendee));
if (!attendees) throw new HttpError({ statusCode: 404, message: "No attendees were found" });
+2 -2
View File
@@ -52,10 +52,10 @@ import { schemaAttendeeCreateBodyParams, schemaAttendeeReadPublic } from "~/lib/
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const body = schemaAttendeeCreateBodyParams.parse(req.body);
if (!isAdmin) {
if (!isSystemWideAdmin) {
const userBooking = await prisma.booking.findFirst({
where: { userId, id: body.bookingId },
select: { id: true },
@@ -5,10 +5,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin, query } = req;
const { userId, isSystemWideAdmin, query } = req;
const { id } = schemaQueryIdParseInt.parse(query);
/** 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,
* 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) {
const { userId, isAdmin } = req;
if (isAdmin) return;
const { userId, isSystemWideAdmin } = req;
if (isSystemWideAdmin) return;
const data = schemaAvailabilityCreateBodyParams.parse(req.body);
const schedule = await prisma.schedule.findFirst({
where: { userId, id: data.scheduleId },
+2 -2
View File
@@ -189,7 +189,7 @@ const availabilitySchema = z
);
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);
if (!teamId)
return getUserAvailability({
@@ -224,7 +224,7 @@ async function handler(req: NextApiRequest) {
const isUserAdminOrOwner =
memberRoles[reqUserId] == MembershipRole.ADMIN ||
memberRoles[reqUserId] == MembershipRole.OWNER ||
isAdmin;
isSystemWideAdmin;
if (!isUserAdminOrOwner) throw new HttpError({ statusCode: 403, message: "Forbidden" });
const availabilities = members.map(async (user) => {
return {
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
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.
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Find all references where the user has bookings
const bookingReference = await prisma.bookingReference.findFirst({
where: { id, booking: { userId } },
@@ -60,12 +60,12 @@ import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransform
* description: Authorization information is missing or invalid.
*/
export async function patchHandler(req: NextApiRequest) {
const { query, body, isAdmin, userId } = req;
const { query, body, isSystemWideAdmin, userId } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const data = schemaBookingEditBodyParams.parse(body);
/* If user tries to update bookingId, we run extra checks */
if (data.bookingId) {
const args: Prisma.BookingFindFirstOrThrowArgs = isAdmin
const args: Prisma.BookingFindFirstOrThrowArgs = isSystemWideAdmin
? /* If admin, we only check that the booking exists */
{ where: { id: data.bookingId } }
: /* 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
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const args: Prisma.BookingReferenceFindManyArgs = isAdmin ? {} : { where: { booking: { userId } } };
const { userId, isSystemWideAdmin } = req;
const args: Prisma.BookingReferenceFindManyArgs = isSystemWideAdmin
? {}
: { where: { booking: { userId } } };
const data = await prisma.bookingReference.findMany(args);
return { booking_references: data.map((br) => schemaBookingReferenceReadPublic.parse(br)) };
}
@@ -62,9 +62,9 @@ import {
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
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 */
{ where: { id: body.bookingId } }
: /* 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 prisma from "@calcom/prisma";
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin, query } = req;
if (isAdmin) {
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin, query } = req;
if (isSystemWideAdmin) {
return;
}
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({
where: { id: userId },
include: {
@@ -43,7 +61,7 @@ async function authMiddleware(req: NextApiRequest) {
});
if (!teamBookings) {
throw new HttpError({ statusCode: 401, message: "You are not authorized" });
throw new HttpError({ statusCode: 403, message: "You are not authorized" });
}
}
}
+16 -2
View File
@@ -5,6 +5,7 @@ import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
import { schemaBookingEditBodyParams, schemaBookingReadPublic } from "~/lib/validations/booking";
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>) {
const { isAdmin } = req;
if (body.userId && !isAdmin) {
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
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
throw new HttpError({
statusCode: 403,
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);
@@ -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);
})
);
+94 -17
View File
@@ -5,6 +5,11 @@ import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
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 { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
@@ -108,7 +113,11 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
* 404:
* description: No bookings were found
*/
type GetAdminArgsType = {
adminDidQueryUserIds?: boolean;
requestedUserIds: number[];
userId: number;
};
/**
* Constructs the WHERE clause for Prisma booking findMany operation.
*
@@ -161,12 +170,18 @@ function buildWhereClause(
};
}
async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
export async function handler(req: NextApiRequest) {
const {
userId,
isSystemWideAdmin,
isOrganizationOwnerOrAdmin,
pagination: { take, skip },
} = req;
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
const args: Prisma.BookingFindManyArgs = {};
args.take = take;
args.skip = skip;
args.include = {
attendees: true,
user: true,
@@ -182,19 +197,32 @@ async function handler(req: NextApiRequest) {
const filterByAttendeeEmails = attendeeEmails.length > 0;
/** Only admins can query other users */
if (isAdmin) {
if (req.query.userId) {
if (isSystemWideAdmin) {
if (req.query.userId || filterByAttendeeEmails) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
const users = await prisma.user.findMany({
where: { id: { in: userIds } },
select: { email: true },
});
const userEmails = users.map((u) => u.email);
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
} else if (filterByAttendeeEmails) {
args.where = buildWhereClause(null, attendeeEmails, [], []);
const requestedUserIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
const systemWideAdminArgs = {
adminDidQueryUserIds: !!req.query.userId,
requestedUserIds,
userId,
};
const { userId: argUserId, userIds, userEmails } = await handleSystemWideAdminArgs(systemWideAdminArgs);
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 {
const user = await prisma.user.findUnique({
where: { id: userId },
@@ -203,7 +231,7 @@ async function handler(req: NextApiRequest) {
},
});
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, [], []);
}
@@ -225,4 +253,53 @@ async function handler(req: NextApiRequest) {
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));
+13 -2
View File
@@ -4,6 +4,8 @@ import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBooking
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
import { defaultResponder } from "@calcom/lib/server";
import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
/**
* @swagger
* /bookings:
@@ -204,8 +206,17 @@ import { defaultResponder } from "@calcom/lib/server";
* description: Authorization information is missing or invalid.
*/
async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
if (isAdmin) req.userId = req.body.userId || userId;
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
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);
}
@@ -97,9 +97,10 @@ import { schemaConnectedCalendarsReadPublic } from "~/lib/validations/connected-
*/
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];
@@ -1,6 +1,6 @@
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 { defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
@@ -63,7 +63,7 @@ async function handler(req: NextApiRequest) {
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({
where: {
@@ -1,7 +1,7 @@
import type { NextApiRequest } from "next";
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 { symmetricDecrypt } from "@calcom/lib/crypto";
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 || "")
);
const key = minimumTokenResponseSchema.parse(decryptedKey);
const key = OAuth2UniversalSchema.parse(decryptedKey);
// Need to get app type
const app = await prisma.app.findUnique({
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
// 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
const eventTypeCustomInput = await prisma.eventTypeCustomInput.findFirst({
where: { id, eventType: { userId } },
+4 -2
View File
@@ -29,8 +29,10 @@ import { schemaEventTypeCustomInputPublic } from "~/lib/validations/event-type-c
* description: No eventTypeCustomInputs were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const args: Prisma.EventTypeCustomInputFindManyArgs = isAdmin ? {} : { where: { eventType: { userId } } };
const { userId, isSystemWideAdmin } = req;
const args: Prisma.EventTypeCustomInputFindManyArgs = isSystemWideAdmin
? {}
: { where: { eventType: { userId } } };
const data = await prisma.eventTypeCustomInput.findMany(args);
return { event_type_custom_inputs: data.map((v) => schemaEventTypeCustomInputPublic.parse(v)) };
}
+2 -2
View File
@@ -80,10 +80,10 @@ import {
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
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. */
const eventType = await prisma.eventType.findFirst({
where: { id: eventTypeId, userId },
@@ -6,9 +6,9 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
if (isAdmin) return;
if (isSystemWideAdmin) return;
const userEventTypes = await prisma.eventType.findMany({
where: { userId },
select: { id: true },
@@ -83,10 +83,10 @@ type UserCredentialType = {
};
export async function patchHandler(req: NextApiRequest) {
const { userId, isAdmin, query, body } = req;
const { userId, isSystemWideAdmin, query, body } = req;
const { id } = schemaQueryIdParseInt.parse(query);
const parsedBody = schemaDestinationCalendarEditBodyParams.parse(body);
const assignedUserId = isAdmin ? parsedBody.userId || userId : userId;
const assignedUserId = isSystemWideAdmin ? parsedBody.userId || userId : userId;
validateIntegrationInput(parsedBody);
const destinationCalendarObject: DestinationCalendarType = await getDestinationCalendar(id, prisma);
@@ -61,11 +61,11 @@ import {
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, body } = req;
const { userId, isSystemWideAdmin, body } = req;
const parsedBody = schemaDestinationCalendarCreateBodyParams.parse(body);
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 */
const userCredentials = await prisma.credential.findMany({
@@ -120,19 +120,20 @@ async function postHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest, userId: number) {
const { isAdmin } = req;
const { isSystemWideAdmin } = req;
const body = schemaDestinationCalendarCreateBodyParams.parse(req.body);
/* Non-admin users can only create destination calendars for themselves */
if (!isAdmin && body.userId)
if (!isSystemWideAdmin && body.userId)
throw new HttpError({
statusCode: 401,
message: "ADMIN required for `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*/
if (!isAdmin && body.eventTypeId) {
if (!isSystemWideAdmin && body.eventTypeId) {
const ownsEventType = await prisma.eventType.findFirst({ where: { id: body.eventTypeId, userId } });
if (!ownsEventType) throw new HttpError({ statusCode: 401, message: "Unauthorized" });
}
+36
View File
@@ -27,6 +27,37 @@ const swaggerHandler = withSwagger({
$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: {
properties: {
id: {
@@ -57,6 +88,11 @@ const swaggerHandler = withSwagger({
type: "string",
example: "Europe/London",
},
fromReschedule: {
type: "string",
nullable: true,
format: "uuid",
},
attendees: {
type: "array",
items: {
@@ -46,9 +46,9 @@ export async function deleteHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
if (isAdmin) return;
if (isSystemWideAdmin) return;
/** Only event type owners can delete it */
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
@@ -87,7 +87,7 @@ async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
req: NextApiRequest,
eventType: (T & Partial<Omit<T, keyof BaseEventTypeCheckPermissions>>) | null
) {
if (req.isAdmin) return true;
if (req.isSystemWideAdmin) return true;
if (eventType?.teamId) {
req.query.teamId = String(eventType.teamId);
await canAccessTeamEventOrThrow(req, {
@@ -237,9 +237,9 @@ export async function patchHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaEventTypeBaseBodyParams>) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
if (isAdmin) return;
if (isSystemWideAdmin) return;
/** Only event type owners can modify it */
const eventType = await prisma.eventType.findFirst({ where: { id, userId } });
if (!eventType) throw new HttpError({ statusCode: 403, message: "Forbidden" });
+4 -4
View File
@@ -43,10 +43,10 @@ import getCalLink from "./_utils/getCalLink";
* description: No event types were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
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.
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
const data = await prisma.eventType.findMany({
@@ -74,9 +74,9 @@ async function getHandler(req: NextApiRequest) {
};
}
// TODO: Extract & reuse.
function extractUserIdsFromQuery({ isAdmin, query }: NextApiRequest) {
function extractUserIdsFromQuery({ isSystemWideAdmin, query }: NextApiRequest) {
/** Guard: Only admins can query other users */
if (!isAdmin) {
if (!isSystemWideAdmin) {
throw new HttpError({ statusCode: 401, message: "ADMIN required" });
}
const { userId: userIdOrUserIds } = schemaQuerySingleOrMultipleUserIds.parse(query);
+7 -7
View File
@@ -265,7 +265,7 @@ import ensureOnlyMembersAsHosts from "./_utils/ensureOnlyMembersAsHosts";
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin, body } = req;
const { userId, isSystemWideAdmin, body } = req;
const {
hosts = [],
@@ -291,7 +291,7 @@ async function postHandler(req: NextApiRequest) {
await checkUserMembership(req);
}
if (isAdmin && parsedBody.userId) {
if (isSystemWideAdmin && parsedBody.userId) {
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
}
@@ -311,18 +311,18 @@ async function postHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest) {
const { isAdmin } = req;
const { isSystemWideAdmin } = req;
const body = schemaEventTypeCreateBodyParams.parse(req.body);
/* Non-admin users can only create event types for themselves */
if (!isAdmin && body.userId)
if (!isSystemWideAdmin && body.userId)
throw new HttpError({
statusCode: 401,
message: "ADMIN required for `userId`",
});
if (
body.teamId &&
!isAdmin &&
!(await canUserAccessTeamWithRole(req.userId, isAdmin, body.teamId, {
!isSystemWideAdmin &&
!(await canUserAccessTeamWithRole(req.userId, isSystemWideAdmin, body.teamId, {
in: [MembershipRole.OWNER, MembershipRole.ADMIN],
}))
)
@@ -331,7 +331,7 @@ async function checkPermissions(req: NextApiRequest) {
message: "ADMIN required for `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" });
}
@@ -10,9 +10,9 @@ export default async function checkTeamEventEditPermission(
req: NextApiRequest,
body: Pick<z.infer<typeof schemaEventTypeCreateBodyParams>, "teamId" | "userId">
) {
const { isAdmin } = req;
const { isSystemWideAdmin } = req;
let userId = req.userId;
if (isAdmin && body.userId) {
if (isSystemWideAdmin && body.userId) {
userId = body.userId;
}
if (body.teamId) {
+3 -3
View File
@@ -58,10 +58,10 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
}
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
const { userId, isAdmin } = req;
if (isAdmin) return;
const { userId, isSystemWideAdmin } = req;
if (isSystemWideAdmin) return;
// 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`" });
// Only team OWNERS and ADMINS can add other members
const membership = await prisma.membership.findFirst({
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { membershipIdSchema } from "~/lib/validations/membership";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { teamId } = membershipIdSchema.parse(req.query);
// Admins can just skip this check
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Only team members can modify a membership
const membership = await prisma.membership.findFirst({ where: { userId, teamId } });
if (!membership) throw new HttpError({ statusCode: 403, message: "Forbidden" });
@@ -43,10 +43,10 @@ export async function deleteHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest) {
const { isAdmin, userId, query } = req;
const { isSystemWideAdmin, userId, query } = req;
const userId_teamId = membershipIdSchema.parse(query);
// Admin User can do anything including deletion of Admin Team Member in any team
if (isAdmin) {
if (isSystemWideAdmin) {
return;
}
@@ -52,11 +52,11 @@ export async function patchHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { userId: queryUserId, teamId } = membershipIdSchema.parse(req.query);
const data = membershipEditBodySchema.parse(req.body);
// Admins can just skip this check
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Only the invited user can accept the invite
if ("accepted" in data && queryUserId !== userId)
throw new HttpError({
+9 -7
View File
@@ -46,26 +46,28 @@ async function getHandler(req: NextApiRequest) {
* Returns requested users IDs only if admin, otherwise return only current user ID
*/
function getUserIds(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
/** Only admins can query other users */
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isAdmin && req.query.userId) {
if (!isSystemWideAdmin && req.query.userId)
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isSystemWideAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
return userIds;
}
// 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
*/
function getTeamIds(req: NextApiRequest) {
const { isAdmin } = req;
const { isSystemWideAdmin } = req;
/** Only admins can query other teams */
if (!isAdmin && req.query.teamId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isAdmin && req.query.teamId) {
if (!isSystemWideAdmin && req.query.teamId)
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isSystemWideAdmin && req.query.teamId) {
const query = schemaQuerySingleOrMultipleTeamIds.parse(req.query);
const teamIds = Array.isArray(query.teamId) ? query.teamId : [query.teamId];
return teamIds;
+3 -3
View File
@@ -37,11 +37,11 @@ async function postHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest) {
const { userId, isAdmin } = req;
if (isAdmin) return;
const { userId, isSystemWideAdmin } = req;
if (isSystemWideAdmin) return;
const body = membershipCreateBodySchema.parse(req.body);
// 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`" });
// Only team OWNERS and ADMINS can add other members
const membership = await prisma.membership.findFirst({
@@ -6,10 +6,10 @@ import prisma from "@calcom/prisma";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { id } = schemaQueryIdParseInt.parse(req.query);
// Admins can just skip this check
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Check if the current user can access the schedule
const schedule = await prisma.schedule.findFirst({
where: { id, userId },
@@ -91,8 +91,8 @@ export async function patchHandler(req: NextApiRequest) {
}
async function checkPermissions(req: NextApiRequest, body: z.infer<typeof schemaSingleScheduleBodyParams>) {
const { isAdmin } = req;
if (isAdmin) return;
const { isSystemWideAdmin } = req;
if (isSystemWideAdmin) return;
if (body.userId) {
throw new HttpError({ statusCode: 403, message: "Non admin cannot change the owner of a schedule" });
}
+4 -4
View File
@@ -77,17 +77,17 @@ export const schemaUserIds = z
*/
async function handler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const args: Prisma.ScheduleFindManyArgs = isAdmin ? {} : { where: { userId } };
const { userId, isSystemWideAdmin } = req;
const args: Prisma.ScheduleFindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
args.include = { availability: true };
if (!isAdmin && req.query.userId)
if (!isSystemWideAdmin && req.query.userId)
throw new HttpError({
statusCode: 401,
message: "Unauthorized: Only admins can query other users",
});
if (isAdmin && req.query.userId) {
if (isSystemWideAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
args.where = { userId: { in: userIds } };
+3 -3
View File
@@ -80,14 +80,14 @@ import { schemaCreateScheduleBodyParams, schemaSchedulePublic } from "~/lib/vali
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const body = schemaCreateScheduleBodyParams.parse(req.body);
let args: Prisma.ScheduleCreateArgs = { data: { ...body, userId } };
/* If ADMIN we create the schedule for selected user */
if (isAdmin && body.userId) args = { data: { ...body, userId: body.userId } };
if (isSystemWideAdmin && body.userId) args = { data: { ...body, userId: body.userId } };
if (!isAdmin && body.userId)
if (!isSystemWideAdmin && body.userId)
throw new HttpError({ statusCode: 403, message: "ADMIN required for `userId`" });
// We create default availabilities for the schedule
@@ -5,10 +5,10 @@ import { HttpError } from "@calcom/lib/http-error";
import { selectedCalendarIdSchema } from "~/lib/validations/selected-calendar";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { userId: queryUserId } = selectedCalendarIdSchema.parse(req.query);
// Admins can just skip this check
if (isAdmin) return;
if (isSystemWideAdmin) return;
// Check if the current user requesting is the same as the one being requested
if (userId !== queryUserId) throw new HttpError({ statusCode: 403, message: "Forbidden" });
}
@@ -53,14 +53,15 @@ import {
* description: Authorization information is missing or invalid.
*/
export async function patchHandler(req: NextApiRequest) {
const { query, isAdmin } = req;
const { query, isSystemWideAdmin } = req;
const userId_integration_externalId = selectedCalendarIdSchema.parse(query);
const { userId: bodyUserId, ...data } = schemaSelectedCalendarUpdateBodyParams.parse(req.body);
const args: Prisma.SelectedCalendarUpdateArgs = { where: { userId_integration_externalId }, data };
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 };
await prisma.user.findFirstOrThrow({ where });
args.data.userId = bodyUserId;
@@ -32,13 +32,14 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
* description: No selected calendars were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
/* Admin gets all selected calendar by default, otherwise only the user's ones */
const args: Prisma.SelectedCalendarFindManyArgs = isAdmin ? {} : { where: { userId } };
const args: Prisma.SelectedCalendarFindManyArgs = isSystemWideAdmin ? {} : { where: { userId } };
/** Only admins can query other users */
if (!isAdmin && req.query.userId) throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isAdmin && req.query.userId) {
if (!isSystemWideAdmin && req.query.userId)
throw new HttpError({ statusCode: 403, message: "ADMIN required" });
if (isSystemWideAdmin && req.query.userId) {
const query = schemaQuerySingleOrMultipleUserIds.parse(req.query);
const userIds = Array.isArray(query.userId) ? query.userId : [query.userId || userId];
args.where = { userId: { in: userIds } };
@@ -50,13 +50,14 @@ import {
* description: Authorization information is missing or invalid.
*/
async function postHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { userId: bodyUserId, ...body } = schemaSelectedCalendarBodyParams.parse(req.body);
const args: Prisma.SelectedCalendarCreateArgs = { data: { ...body, userId } };
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 };
await prisma.user.findFirstOrThrow({ where });
args.data.userId = bodyUserId;
@@ -8,10 +8,10 @@ import { MembershipRole } from "@calcom/prisma/enums";
import { schemaQueryTeamId } from "~/lib/validations/shared/queryTeamId";
async function authMiddleware(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { teamId } = schemaQueryTeamId.parse(req.query);
/** Admins can skip the ownership verification */
if (isAdmin) return;
if (isSystemWideAdmin) return;
/** Non-members will see a 404 error which may or not be the desired behavior. */
await prisma.team.findFirstOrThrow({
where: { id: teamId, members: { some: { userId } } },
@@ -22,24 +22,24 @@ export async function checkPermissions(
req: NextApiRequest,
role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER
) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { teamId } = schemaQueryTeamId.parse({
teamId: req.query.teamId,
version: req.query.version,
apiKey: req.query.apiKey,
});
return canUserAccessTeamWithRole(userId, isAdmin, teamId, role);
return canUserAccessTeamWithRole(userId, isSystemWideAdmin, teamId, role);
}
export async function canUserAccessTeamWithRole(
userId: number,
isAdmin: boolean,
isSystemWideAdmin: boolean,
teamId: number,
role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER
) {
const args: Prisma.TeamFindFirstArgs = { where: { id: teamId } };
/** If not ADMIN then we check if the actual user belongs to team and matches the required role */
if (!isAdmin) args.where = { ...args.where, members: { some: { userId, role } } };
if (!isSystemWideAdmin) args.where = { ...args.where, members: { some: { userId, role } } };
const team = await prisma.team.findFirst(args);
if (!team) throw new HttpError({ statusCode: 401, message: `Unauthorized: ${role.toString()} required` });
return team;
+2 -2
View File
@@ -37,11 +37,11 @@ import { schemaTeamReadPublic } from "~/lib/validations/team";
* description: Team was not found
*/
export async function getHandler(req: NextApiRequest) {
const { isAdmin, userId } = req;
const { isSystemWideAdmin, userId } = req;
const { teamId } = schemaQueryTeamId.parse(req.query);
const where: Prisma.TeamWhereInput = { id: teamId };
// Non-admins can only query the teams they're part of
if (!isAdmin) where.members = { some: { userId } };
if (!isSystemWideAdmin) where.members = { some: { userId } };
const data = await prisma.team.findFirstOrThrow({ where });
return { team: schemaTeamReadPublic.parse(data) };
}
@@ -107,7 +107,7 @@ export async function patchHandler(req: NextApiRequest) {
if (IS_TEAM_BILLING_ENABLED) {
const checkoutSession = await purchaseTeamOrOrgSubscription({
teamId: _team.id,
seats: _team.members.length,
seatsUsed: _team.members.length,
userId,
pricePerSeat: null,
});
@@ -42,13 +42,13 @@ const querySchema = z.object({
* description: No event types were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const { teamId } = querySchema.parse(req.query);
const args: Prisma.EventTypeFindManyArgs = {
where: {
team: isAdmin
team: isSystemWideAdmin
? {
id: teamId,
}
@@ -21,7 +21,7 @@ const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
user: {
id: req.userId,
username: "" /* Not used in this context */,
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
role: req.isSystemWideAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
profile: {
id: null,
organizationId: null,
+2 -2
View File
@@ -30,10 +30,10 @@ import { schemaTeamsReadPublic } from "~/lib/validations/team";
* description: No teams were found
*/
async function getHandler(req: NextApiRequest) {
const { userId, isAdmin } = req;
const { userId, isSystemWideAdmin } = req;
const where: Prisma.TeamWhereInput = {};
// If user is not ADMIN, return only his data.
if (!isAdmin) where.members = { some: { userId } };
if (!isSystemWideAdmin) where.members = { some: { userId } };
const data = await prisma.team.findMany({ where });
return { teams: schemaTeamsReadPublic.parse(data) };
}

Some files were not shown because too many files have changed in this diff Show More