Compare commits

...
290 Commits
Author SHA1 Message Date
Omar López 3a46375fed chore: v4.3.5 (#16096)
release.yml / release (push) Canceled after 0s
2024-08-06 12:42:12 -07:00
sean-brydon 07de77ddc6 Remove feature for now (#16098) 2024-08-06 18:14:12 +00:00
cf45443726 fix: Set user's timezone as fallback to event's timezone (#16065)
* fix: Set user's timezone as fallback to event's timezone

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* fix: Use find method instead of filter

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* chore: Remove user default schedule ID check

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* revert error message

* fix

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-08-06 16:06:54 +00:00
Peer Richelsen 629b9391dd Update README.md (#16049) 2024-08-06 16:02:08 +00:00
25da11c497 fix: issue in the event location field (#15229)
* fix: issues in event location field

* Update EventSetupTab.tsx

* Update EventSetupTab.tsx

* update

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-08-06 15:46:08 +00:00
Imamuzzaki Abu Salamandsean-brydon 003e2630e7 chore: Fix typo in CSS class name for signup page and Calendar (#16059)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-08-06 15:42:02 +00:00
Omar López 4eef7138c1 fix: Wrong tracking event for bookings (#16094) 2024-08-06 15:36:33 +00:00
Pedro Henrique Braga 253d0174be test: Create 5 tests cases for POST /api/event-types endpoint (#16082)
* test: Add tests for POST /api/event-types endpoint

* revert: undo last unintended change in yarn.lock

* revert: undo last unintended change in package.json
2024-08-06 15:36:21 +00:00
Peer RichelsenandAlex van Andel 5ee5c117c4 replaced discord with github discussions (#16092)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-08-06 15:31:32 +00:00
Alex van Andelandsean-brydon 0f268bbef7 chore: add a timeout of 2s to the licence key service (#16027)
* chore: add a timeout of 2s to the licence key service

* Fix unit test

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-08-06 15:28:47 +00:00
Anik Dhabal Babu d342e3f9e0 fix: type error that is visible now (#16090)
* fix type error

* fix
2024-08-06 15:09:04 +00:00
Vijay 7f1e20fe19 fix: #16041 team eventtype create from api (#16058)
* update for teameventType creation from api

* update to set userid null for team event
2024-08-06 13:48:01 +02:00
Amit Sharma 3078423848 chore: update null noShowHost in Booking to false (#15891) 2024-08-06 13:47:01 +02:00
MohitandAnik Dhabal Babu 9953d73444 fix: incorrect week-start input value (#16057)
* fix: incorrect week start  input value

* fix the issue

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-08-06 13:46:44 +02:00
100136aae7 fix: #15857 add managed by tooltip (#15861)
* managed by tooltip

* updated for team admin

* updated for typechecks and naming

* for multiple admins

* update to show team link when more than 2 admins

* nit/chore : variable naming

* improvement

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
2024-08-06 13:46:24 +02:00
Imamuzzaki Abu Salam 3c6e9aaa63 chore: Refactor CloseComSetup form submission logic (#16062)
The commit refactors the form submission logic in the CloseComSetup component to use the URLSearchParams API for constructing the URL and removes unnecessary code.
2024-08-06 13:43:17 +02:00
Joe Au-YeungandCarinaWolli 8fa39f2ae5 feat: Round robin reassignment via round robin algorithm [CAL-3138] (#14308)
* add reassign host to edit dropdown

* add dialog to reassign rr host

* Design improvements

* add translations

* only show reassign for admin/owner

* add first version of email template

* Init rr assign endpoint

* Pass booking information to rr assign endpoint

* Only allow attendee of booking to reassign themselves

* Send email to reassigned and cancelled member

* On reassign change calendar events

* Add workflows for new host

* On reassign new host - rr

* Fix icon

* Abstract reassignment logic

* Update calendar invite

* Add tests

* Clean up

* Merge with `main`

* Type fixes

* Add back dialog and handle no available hosts

* Handle if rr host is not organizer

* Fix calendar invites when organizer doesn't change

* Clean up

* Clean up

* Type fixes

* Type fixes

* Type fixes

* Type fixes

* Type fix

* Type fixes

* Type fixes

* Add custom responses to evt object

* Type fixes

* Type fix

* Type fix

* Type fixes

* Type fixes

* Type fix

* Type fix

* Update tests

* Type fixes

* Fix tests

* Fix tests

* Add booking repository

* Fix tests

* Fix tests

* Add doesUserIdHaveAccessToBooking for user

* Add check if user is team admin

* Check user permission tRPC route

* Type fixes

* Correct Promise.all

* UI fixes

* UI fixes

* Remove unused assigned hosts prop

* Type fix

* Remove unused frontend code

* Include user priority

* Fallback to event type users for older event types

* Get booking workflow reminder

* Revert back to eventType.hosts

* Fix lint

* Handle changing workflows

* Type fixes

* Type fixes

* Type fix

* Fix tests

* Update new booking imports

* Fix imports

* Type fix

* Type fix

* Fix adding all members to reassignment emails

* Fix cancelled RR emails to show old host

* Ensure consistent event titles

* Send new event workflows to new host

* Change event name if organizer changed

* Fix query error

* Delete old booking reference when reassigning

* Type fixes

* Type fixes

* Fix test

* Rename func isAdminOfTeamOrParentOrg

* Select specific workflow fields

* Address workflow feedback

* Delete const

* Address feedback

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-08-06 13:42:46 +02:00
Alex van AndelandKeith Williams 3aca72fb06 chore: Reapply "fix: bookings made via API don't include the specified locat… (#15461)
This reverts commit d8de919acb.

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-08-06 13:41:45 +02:00
Alex van Andel c46905e9a7 fix: next.js console warning due to output:standalone (#16087) 2024-08-06 11:38:24 +00:00
Hariom Balhara 5ce96c7704 fix: Prefill of Phone type option field (#16086) 2024-08-06 11:05:08 +00:00
Lauris Skraucis 23eb77b8ac fix: re-booked booking metadata (#16074)
* fix: rescheduled booking metadata is overwritten by original booking metadata

* chore: bump v2 platform libraries to 0.0.23

* tests: re-scheduled booking metadata update
2024-08-06 11:15:32 +02:00
Alex van Andel e63edc9d36 fix: testId -> testid (#16081)
* fix: testId -> testid

* fix other errors in caps
2024-08-06 11:34:02 +05:30
sean-brydonandPeer Richelsen d33681eddd re-design (#16048)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-08-06 01:35:15 +01:00
Dolev Hadarandzomars e7816cfb0c perf: improve typescript perf by pre-generating the router types (#15929)
Co-authored-by: zomars <zomars@me.com>
2024-08-05 16:49:19 -07:00
Omar López a268d8743d chore: upgrades playwright to 1.45.3 (#16034) 2024-08-05 23:38:45 +01:00
45db5c0d5e refactor: event types infinite scroll (#15949)
* chore

* chore

* feat: paginated event types

* fix: event types page

* fix: k bar

* fix: event type order

* fix: type error

* chore: remove commented code

* chore: type err

* chore: type err and feedback

* feat: add old component

* chore: missing import

* fix: add isInfiniteScrollEnabled prop

* chore: type err

* chore: type error

* feat: auto fetch next page when button is in view

* Update packages/lib/server/repository/eventType.ts

* Update packages/lib/server/repository/eventType.ts

* chore: feedback

* fix: managed event types

* fix: Review fixes for event-types-infinite-scroll (#16047)

* Review fixes

* chore: missing import

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>

* chore: type error

* chore

* fix: delete event type

* fix: create event dialog

* fix: create and duplicate dialog

* chore: simplify return type

* fix: type

* chore: invalidate query

* chore: remove query

* fix duplicated event not showing.

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-08-05 16:01:03 +04:00
Anik Dhabal BabuandHariom 583661a15d feat: Default to showing the next month with availability (#15292)
* feat: Default to showing the next month with availability

* update

* Update DatePicker.tsx

* dpdate

* Update DatePicker.tsx

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-08-05 09:31:45 +00:00
Anik Dhabal BabuandHariom Balhara 461d5e0535 feat: Make event duration a dynamic text variable (#15262)
* feat: Make event duration a dynamic text variable

* Update common.json

* final update

* update

* fix type error

* fix unit test

* refactor and update

* For now log the situation when duration isnt a number

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-08-05 09:07:46 +00:00
099d830e7e feat: Add URL-type input in booking question field (#15481)
* feat: Add URL-type input in booking question field

* fix type error

* Update common.json

* fix requested changes

* small update

---------

Co-authored-by: Rajiv Sahal <sahalrajiv6900@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-08-05 12:53:53 +05:30
Morgan add59896f5 fix: platform orgs shouldn't have org redirect (#16032)
* fix: platform orgs shouldn't have custom domains

* fixup! Merge branch 'main' into no-redirect-for-platform-orgs

* fixup! Merge branch 'main' into no-redirect-for-platform-orgs
2024-08-02 20:34:11 +03:00
03b97fdb51 fix: typo in license service + move to a noop (#16029)
* fix/typoe

* use noop

* Update packages/features/ee/common/server/LicenseKeyService.ts

* wip - try unbork e2e

* update conditional

* use e2e value for NOOP resolve value

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-08-02 17:10:58 +00:00
Morgan 4c576fbd57 fix: hide org upgrade banner for platform orgs (#16031) 2024-08-02 14:45:59 +02:00
Hariom Balhara 06ce1988f3 fix: Future Limit Calculations (#15825) 2024-08-02 18:00:50 +05:30
Morgan c059d79a51 feat: api v2 webhooks for users and event-types (#15996)
* feat: webhooks for users

* fixup! feat: webhooks for users

* feat: webhooks for user event types

* feat: webhooks for user event types

* fixup! Merge branch 'main' into feat-webhooks-api-v2

* doc

* chore: split webhook service

* chore: webhook repo only depends on prisma

* chore: split webhook outputs

* fixup! chore: split webhook outputs

* chore: describe payload template

* chore: pipe webhook input and output

* chore: use partialType for update dtos

* chore: improve dto
2024-08-02 12:18:35 +00:00
b91c767d0c feat: SysAdmin API Guard for Org Admins (#15867)
* adds column in schema

* adds org setting to enable/disable admin api

* adds guard to v1 v2

* add update query for isAdminAPIEnabled to be true for existing orgs

* adds contact sales

* fix typo

* fix tests

* fixup v2 guard

* v1 fix

* fix and add integration test

* fixes tests --WIP

* fix test --WIP

* fixupgit add src/modules

* WIP

* fix

* fix: e2e test org teams

* fix: e2e test org users

* review feedback

* requested change

* fix

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-08-02 14:35:54 +04:00
sean-brydonandUdit Takkar bb29edd30f add gap (#16000)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-08-02 09:05:00 +00:00
Omar López b9a8b613bb fix: don't wait for telemetry for booking critical path (#16039) 2024-08-01 17:30:46 -07:00
Morgan 76377d2d2c chore: disconnect prisma on module destroy apiv2 (#16018) 2024-08-01 09:41:50 +00:00
Anik Dhabal Babu f4f09eaf9e fix: bulk update event type dialog logic (#16014)
* improvise code

* bulk update model issue

* Update BulkEditDefaultForEventsModal.tsx
2024-08-01 08:41:41 +03:00
Omar López 89c08868f6 chore: v4.3.4 (#16010)
release.yml / release (push) Canceled after 0s
2024-07-31 21:01:49 +00:00
Omar López ebab3b701d revert: "fix: meeting link is not updated and broken ui in edit location dialog" (#16011) 2024-07-31 23:16:45 +03:00
Anik Dhabal BabuandKeith Williams 9ffa4b8477 fix: meeting link is not updated and broken ui in edit location dialog (#15503)
* fix: meeting link is not updated

* fix: broken ui in edit location dialog

* Update editLocation.handler.ts

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-31 19:05:20 +00:00
Amit Sharma c656063b06 fix: incorrect variable (#16002) 2024-07-31 13:48:32 +00:00
Hariom BalharaandUdit Takkar 5b7f2a6ffc fix: Reschedule URL for an org migrated/moved user's booking. (#15736)
* Fix reschedule URl

* Add e2e for org

* Add team booking reschedule test

* Add unit tests

* Remove unnecessary select

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-31 14:25:46 +02:00
Imamuzzaki Abu SalamandAmit Sharma 63c7e4b55a test: add tests for components/editor (#15938)
* test: add VariableNode test suite

* test: add ToolbarPlugin test suite

* test: add AutoLinkPlugin test suite

* test: add AddVariablesDropdown test suite

* test: add Editor test suite

* chore: Remove unnecessary comments in ToolbarPlugin.test.tsx

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-31 14:25:00 +02:00
Carina WollendorferandCarinaWolli 24e5226e3e fix: issues with verified emails in workflow (#15965)
* fix verified emails

* review feedback

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-07-31 11:58:14 +00:00
sean-brydon 9f43c5f1e7 add row on click if user can edit (#15998) 2024-07-31 11:12:48 +00:00
MohitandAnik Dhabal Babu b48e3c7bb4 fix: alert message is not positioned properly (#15995)
* fix: correct button functionality in alert component

* add: bottom margin to alert component

* revert alert comp changes

* small update

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-31 00:23:11 +03:00
Morgan a561b805fd chore: add doc + test reschedule booking api v1 (#15907)
* chore: add doc + test reschedule booking api v1

* fixup! Merge branch 'main' into fix-reschedule-booking-api-v1
2024-07-31 00:22:25 +03:00
VijayandAnik Dhabal Babu d21c91c519 fix: #15989 disable country prefill in phone input (#15991)
* default country disabled in phone input

* Update PhoneInput.tsx

* update event-types e2e

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-31 00:20:37 +03:00
Lauris SkraucisandMorgan 41e565fcf8 refactor: platform org event types (#15961)
* chore: platform-libraries export updateNewTeamMemberEventTypes

* chore: platform-libraries export updateNewTeamMemberEventTypes

* chore: v2 api bump platform-libraries to 0.0.22

* fix: add created managed user to assignAllMembers: true team event-types

* fix: add created organizations user to assignAllMembers: true team event-types

* refactor: organization teams chose if team creator is auto-accepted

* refactor: managed users have only 2 default event types with video

* refactor: lowercase schedulingType for creating team event-types

* refactor: lowercase schedulingType for creating team event-types

* feat: separate platform teams by OAuth client

* swagger regenerate

* refactor: delete platform team when oauth client deleted

* revert: keep 4 default event-types

* fix: tests && merge of main into this branch

* fix: typecheck

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-07-30 22:23:31 +03:00
Lauris SkraucisandMorgan 2f946fc5da feat: platform team event-types (#15928)
* fix: dont check OrganizationSettings.orgAutoAcceptEmail uniqueness for platform teams

* feat: display organizationId in OAuthClientCard

* feat: ApiAuthStrategy handle oauth credentials

* refactor: clean up ApiAuthStrategy test

* refactor: use ApiAuthGuard in OauthClientsUsersController and OAuthFlowController

* fix: copying org id

* refactor: more specific error message when api auth

* refactor: auto accept team creator membership

* feat: useTeamEventType and useTeamEventTypes hooks

* refactor: make team event-types public & searchable by eventSlug

* feat: include host name in team event-types hosts response

* fix: isFixed=true by default for COLLECTIVE event hosts

* fix: useTeamEventType eventSlug access

* feat: BookerPlatformWrapper enable team events by exposting orgId and teamId props

* refactor: provide orgId in atoms context

* refactor: use orgId from context in team event-types hooks

* refactor: return teams in useMe

* chore: examples app teams setup

* Revert "refactor: return teams in useMe"

This reverts commit de992ddc9af6ee9a2111938069f5b9c34cc2d8ea.

* Revert "chore: examples app teams setup"

This reverts commit 0766aa21acc25efa2361d38c3f87ddba773a0245.

* feat: useTeams hook

* chore: setup examples app with team event-type

* fix: small fixes

* swagger

* Revert "refactor: provide orgId in atoms context"

This reverts commit f053a498ee6f8fa8ece5ec8d8630c59eda8873e3.

* feat: orgId in atoms context

* feat: PlatformBilling guard

* chore: delete test of the deleted oauth-client-credentials.guard

* refactor: org event-types collective events isFixed always true and priority medium

* refactor: org event-types COLLECTIVE response ignore isFixed and priority as they are same

* fix: organizations event-types e2e

* fix: billing guard

* refactor: tests cleanup

* fix: platform plan guard spec

* refactor: e2e test cleanup

* seed error if not team

* refactor: rename authenticateApiKey to authenticateBearerToken

* refactor: transforming response hosts

* refactor: rename findUniqueByMatchingAutoAcceptEmail to findUniqueNonPlatformOrgsByMatchingAutoAcceptEmail

* refactor: rename findUniqueByMatchingAutoAcceptEmail to findUniqueNonPlatformOrgsByMatchingAutoAcceptEmail

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-07-30 16:58:39 +02:00
06bb7055ac fix: added translations for connect atoms (#15969)
* fix: added translations for connect atoms

* fix: already spelling

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-30 14:34:05 +00:00
Peer Richelsen 1a8492b9c2 chore: fixed a few destructive buttons (#15990) 2024-07-30 16:24:11 +03:00
b8ba95dbb0 feat: min max chars for long text (#15691)
* feat: min max chars for long text

* refactor: remove console log

* refactor: revert redundant change

* refactor: review changes

* fix: min max validation

* fix: fix typo

* refactor: update comments

* refactor: review changes

* refactor: review changes

* test: Add test

* Make maxLength more configurable per field type

* Reduce maxLength to 1000

* Update update-event-type.input.ts

* revert changes in main

* Update update-event-type.input.ts

* refactor get-event-type-public.output.ts

* add e2e tests

* add min max char common.json

* Update FormBuilder.tsx

* refactor changes

* Update fieldTypes.ts

* Update schema.ts

* add min max length in api-request

* Update booking-fields.input.ts

* fix e2e tests

* move min-max-char tests to manage-booking-questions.e2e.ts

* remove test

* reorder imports to fix lint

* fix lint

* fix e2e tests

* Abstract out SupportsLengthcheck type Field

* Revert newline change

* Improve the schema validation a bit and fix undefined in the message issue

* revert: v2 API changes

---------

Co-authored-by: Ayush Mhetre <mhetreayush1719@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
2024-07-30 12:21:27 +00:00
40df4cf511 fix: Add strict checks for email to create an organization (#15976)
* Verify email first

* Only allow company emails for org creation

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-30 11:52:10 +00:00
58cabe974c fix: Add signup disabled flag check on signup route (#15844)
* fix: Add signup disabled flag check on signup api

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* feat: Implement a feature flag check trcp middleware

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* Discard changes to packages/trpc/server/middlewares/featureFlagMiddleware.ts

* Discard changes to packages/trpc/server/routers/viewer/organizations/_router.tsx

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-30 11:26:20 +00:00
Udit TakkarandJoe Au-Yeung 199d95b35f feat: cal ai templates (#15391)
* chore: save progress

* feat: add schema and fieds

* chore: update code

* chore: save

* refactor: code and type error

* feat: use facade + command design pattern

* chore: missing param

* feat: add custom template feature

* refactor: code

* chore: move the default prompt

* fix: type err

* fix: code and ux improvements

* chore: change default template

* fix: ui and typo

* fix: add validation check for my phone number

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-07-30 13:09:12 +02:00
VijayandAnik Dhabal Babu c6febad364 fix: #15968 update to show booking question variables in Available variables (#15981)
* update to show booking question variables in Available variables

* small fix

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-30 08:46:44 +00:00
Morgan 0ddcc068ce fix: bullMq redis connection string apiv2 (#15970)
* fix: bullMq redis connection string apiv2

* fixup! fix: bullMq redis connection string apiv2
2024-07-30 08:27:27 +00:00
Anik Dhabal BabuandSomay Chauhan 3ff3a97c78 feat: ability to provide a reason for event rejection (from email) and some changes (#15098)
* feat: ability to provide reason for event rejection from email

* update

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
2024-07-30 09:35:02 +02:00
MorganandKeith Williams baaa1fa71e chore: add global rate limiting to prevent abuse apiv2 (#15971)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-30 06:34:31 +00:00
27d331ad2d fix: #15967 Show better error when wrong variable is used in custom event name (#15974)
* update to show exact err msg with custom event name

* updated tests for validateCustomEventName

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-29 20:23:53 +03:00
Joe Au-YeungandAnik Dhabal Babu 9a1a86ed3a test: Delete credential endpoint (#15322)
* Init

* Add mock trpc context

* Intro mockDatabaseClient

* Introduce mockDatabaseClient class

* Add delete video app test

* Add calendar test

* Remove unused func from bookingScenario

* Remove console.log

* Add app repository

* Add createMany method to event type repository

* Remove instance of MockDatabaseClient from video test

* Add destination calendar repository

* Remove instances of MockDatabaseClient

* abstract logic to own handler

* Remove dev dependency

* Clean up

* Type fix

* Pull yarn.lock from main

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-29 11:38:58 -04:00
Keith Williams 7902c6157d fix: Broken GitHub workflows (#15979) 2024-07-29 17:35:52 +03:00
sean-brydonandHariom Balhara 92d0fc6eb4 fix: auto accept (#15975)
* fix: auto accept

* Update packages/lib/server/repository/organization.ts

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

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-29 14:18:55 +00:00
Carina WollendorferandCarinaWolli 4d0b417925 fix: type in webhook payload (#15912)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-07-29 13:48:45 +00:00
Keith Williams 9947b5ec85 fix: Next.js bundle analysis (#15903)
* fix: Next.js bundle analysis

* Added the main checkout back
2024-07-29 13:47:50 +00:00
fa06f38d47 fix: refresh connect atoms on token change (#15933)
* added dropdown

* change comment

* fix: added types

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-29 16:31:31 +03:00
Lauris SkraucisandKeith Williams 99a6b1c7eb refactor: v1 orgs unlimited teams (#15908)
* refactor: v1 orgs unlimited teams

* fix: response message ownerId

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-29 16:31:18 +03:00
e3861ad22a feat: No-show bookings - do not count towards a member's round robin assignment (#15323)
* feat: No-show bookings - do not count towards a member's round robin assignment

* filter no show organizer

* remove logs

* fix: filter bookings where host attendee is no show

* fix: use not condition for noShow

* chore: test

* add default false for noShowHost

* Revert "chore: test"

This reverts commit c914a8fc4a14ca9c7d8c4a08ff8b0f463789171c.

* chore: integration test

* fix: noShowHost not working for null

* Update packages/lib/server/getLuckyUser.ts

* chore: update test description

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: gh0st91848 <gh0st91848@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-29 16:05:03 +03:00
Hariom BalharaandKeith Williams 37caa57798 fix: Add CORP and COEP headers to embedded pages (#15275)
* Add CORP headers

* Improve test

* Add unit tests as well

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-29 11:54:31 +02:00
6e66f7f8f8 fix: Assignment reminder modal appears when deleting a team event type (#15544)
* fix: Assignment reminder modal appears when deleting a team event type

* update

* Update EventTypeSingleLayout.tsx

* Update event-types-single-view.tsx

* update

* Early return https://handbook.cal.com/engineering/best-practices/prefer-early-returns

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-07-29 09:52:03 +00:00
Hichem FantarandHariom Balhara 62dd5ea95b feat: Add title attribute to iframe in embed.ts (#15944)
* feat: Add title attribute to iframe in embed.ts

* Update packages/embeds/embed-core/src/embed.ts

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-29 08:14:13 +00:00
Johannes Maendle 365dfe9178 i18n: update translations for "by proceeding, ..." (#15921) 2024-07-29 06:04:33 +00:00
Hichem FantarandKeith Williams 4d89da99a7 feat: pwa ready and add shortcut for quick access (#15957)
* chore: Update site.webmanifest with additional display properties

* chore: Update site.webmanifest with additional display properties

* Update apps/web/public/site.webmanifest

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-29 05:37:31 +00:00
Souptik DattaandKeith Williams 6954ad1ea5 fix: Change minimum number of teams required for org self-serve (#15955)
* fix: Change minimum number of teams required for org self-serve

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* refactor: Use a new constant for displaying org helper text dialogue

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* Update packages/lib/constants.ts

* Update turbo.json

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-29 05:17:17 +00:00
Hichem Fantar 825573da8e feat: add transitions to more components (#15916)
* add transitions to more components

* chore: Remove duplicate class
2024-07-29 08:06:44 +03:00
2c517f4878 fix: getting an error when a team member tries to leave (#15925)
a team

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-29 07:21:56 +03:00
VijayandAnik Dhabal Babu 8508a6f1a0 fix: #15697 Assignment warning modal poping up ,even when hosts are added . (#15699)
* removed reduntand check on assignedusers

* update for managed eventtype

* formatted

* comments

* Updated comments

* extracted checkForEmptyAssignment and added tests

* updated comments

* updated to show dialog also when not yet saved

* update translations

* updated comments

* updated translation

* updated types

* chore: update naming and comments

* Delete apps/web/lib/checkForEmptyAssignment.ts

deleted since file was renamed

* Delete apps/web/test/lib/checkForEmptyAssignment.test.ts

deleted as the file is renamed to be more descriptive

* update to do teamcheck before

* chore: revert name to CheckForEmptyAssignment

* updated check conditions

* updated useEffect dependencies

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-29 07:02:59 +03:00
db03e9db20 style: update text area (#15934)
* chore: update text-editor input

* chore: update text-editor input

* final update

* Update Editor.tsx

---------

Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-29 06:51:53 +03:00
Peer Richelsen e6a16f5b5c chore: Update feature_request.md (#15926)
* Update feature_request.md

* Update feature_request.md
2024-07-26 12:49:19 +00:00
b94aa603c5 chore: Add missing comments for self hosted billing (#15923)
* WIP: UI created to see if it matches RFC idea

* Remove schema as we validate on the backend with old+new key schemas

* Add comment about proxy URL request when on older versions

* feat: create custom axios instance to use signature middleware

* fix: update form to respect new schema

* Add cal signature token to trubo env

* use backend mutation for creating license keys

* add loading state to creation button

* update form to have a success state

* fix type error for console.warning :')

* use api version v1

* Inital setup

* Fix verifyApi Key tests

* Implement tests for LicenseKeyService

* Restore old checkLicense code

* check license key first

* fix imports

* fix url in tests. Implement prisma extention to track creation

* Use data.status for licenseCheck

* extract fetch to class

* improve error

* [wip] per user tracking

* Remove new URL as it was breaking search params

* Use client extention in customPrismaClient

* Remove log

* Remove license required from creation form as itll fail

* Fix annual price calculation

* add api route to .env.example and constants

* re-word instructions in .env.example

* chore: feedback

* Update packages/features/ee/common/server/LicenseKeyService.ts

* Add IS_E2E_ENABLED constant + update tests

* Revert "Add IS_E2E_ENABLED constant + update tests"

This reverts commit d059b73f6272bc06fd2ab5ed505b6d52ea030a56.

* chore: tests fixes

* fixes: unit tests

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

* chore: add missing instructions

---------

Signed-off-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-07-26 10:10:46 +00:00
31e6b88951 fix: Fix teams helper image background color mode (#15918)
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-26 10:25:39 +02:00
Alex van Andel fbff4db2a6 chore: Release v4.3.3 (#15924)
release.yml / release (push) Canceled after 0s
2024-07-26 09:00:00 +01:00
Udit Takkar ba61731689 fix: make max_participants optional (#15920) 2024-07-26 07:08:05 +00:00
sean-brydonandOmar López e16662e655 feat: usage base billed - license key check - move to private api (#15222)
Co-authored-by: Omar López <zomars@me.com>
2024-07-25 13:20:24 -07:00
sean-brydon 53c37a9ddf feat: refactor drawer component (#15892)
* Add sheet component

* refactor user edit sheet

* refactor availbility sheet edit

* Remove tremor raw sheet text

* remove accidently file commit

* fix overflow issue
2024-07-25 12:41:23 +01:00
Morgan 64ef2703a5 feat: org members can fetch their teams apiv2 (#15910)
* feat: org members can fetch their teams apiv2

* fixup! feat: org members can fetch their teams apiv2

* feat: org members can fetch their teams apiv2

* fixup! feat: org members can fetch their teams apiv2

* fixup! fixup! feat: org members can fetch their teams apiv2

* fixup! fixup! fixup! feat: org members can fetch their teams apiv2

* fixup! fixup! fixup! fixup! feat: org members can fetch their teams apiv2
2024-07-25 10:39:43 +00:00
Rajiv SahalandMorgan Vernay a997edd8db chore: update dtos for v2 calendars endpoint (#15904)
* init calendars input

* update request body input type

* fix incorrect naming

* update imprts

* fixup! update imprts

* fixup! fixup! update imprts

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-07-25 08:49:18 +00:00
Peer Richelsen bd73eba528 chore: Update config.yml (#15900) 2024-07-25 09:35:33 +02:00
0d53c2069e feat: team owner/admin, be able to confirm/reject "requires confirmation" team events (#15326)
* feat: eam owner/admin, be able to confirm/reject requir
es confirmation team events

* update

* addressed requested changes

* remove console.log

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-25 09:35:03 +02:00
Souptik Datta 3b559525a8 fix: Unable to invite a user to a team via username (#15868)
* fix: Fix invalid existing username DB query

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* fix: Support adding org team member using username

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* fix: Remove invite by username logic and fix labe to display only email

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* refactor: Remove username support completely from invitation-modal component

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
2024-07-25 07:04:44 +00:00
Peer Richelsen 33e1abf4a7 chore: fixed bug_report.yml (#15901) 2024-07-24 22:55:48 +00:00
059c6c37ba fix google calendar event not deleted on default calendar of cal event change (#14994)
Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@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: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-24 15:34:41 +00:00
Peer Richelsen 14bc489d70 chore: Update bug_report.md (#15894)
* Update bug_report.md

* Apply suggestions from code review
2024-07-24 15:22:45 +00:00
Morgan e8b36d5b0b chore: platform api usage based billing using queue (#15085)
* chore: platform api usage based billing using queue

* fixup! chore: platform api usage based billing using queue

* fixup! fixup! chore: platform api usage based billing using queue

* fixup! Merge branch 'chore-platformapi-billing-queue' of github.com:calcom/cal.com into chore-platformapi-billing-queue

* fixup! fixup! Merge branch 'chore-platformapi-billing-queue' of github.com:calcom/cal.com into chore-platformapi-billing-queue

* fixup! Merge branch 'main' into chore-platformapi-billing-queue

* chore: platform billing by userId

* fix: fix tests requirement team subscirption

* fixup! Merge branch 'main' into chore-platformapi-billing-queue

* fixup! fixup! Merge branch 'main' into chore-platformapi-billing-queue

* fixup! Merge branch 'main' into chore-platformapi-billing-queue

* improve tests logs

* fix: silently close redis and queue

* fixup! Merge branch 'main' into chore-platformapi-billing-queue

* fixup! Merge branch 'main' into chore-platformapi-billing-queue

* fixup! fixup! Merge branch 'main' into chore-platformapi-billing-queue

* fix: upgrade libraries version
2024-07-24 17:10:22 +02:00
Saket kumar singh 5983c960a6 Fix: #15888 (#15890) 2024-07-24 14:03:31 +00:00
Souptik Dattaandunknown 40305b9c5a fix: Change conferencing app-store URL on event types and bookings pages (#15878)
* fix: Change conferencing app-store URL on event types and bookings pages

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* chore: Change conference app store help text

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* chore: Revert app install helper text key for all locales except en

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* delete key and text

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
2024-07-24 14:02:13 +00:00
Lauris Skraucis dcc62c6db0 fix: CreateAvailabilityInput_2024_04_15 Cannot read properties of undefined (#15885) 2024-07-24 12:52:42 +00:00
Lucas Víctor Ferreira de AraújoandAnik Dhabal Babu 07f14e4e14 test: test cases for POST selected-calendars (#15873)
* test-selected_calendars

* Update _post.test.ts

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-24 14:29:09 +02:00
Keith Williams f940f6bd87 chore: Remove unit tests from running after crowd in (#15880)
* chore: Remove unit tests from running after crowd in

* Update permissions on all workflows

* Updated pr permissions

* Added actions:write to top-level pr

* Update .github/workflows/unit-tests.yml

* Removed the linting annotation
2024-07-24 15:18:11 +03:00
Keith Williams ca5dcfd1e8 chore: Add v2 API test suite (#15865)
* chore: Add v2 API test suite

* Changed oauth credentials guard to e2e spec

* Uploading e2e artifacts

* Removed commented line
2024-07-24 11:02:03 +02:00
Lauris Skraucis 324728d4ec refactor: v2 organizations event-types e2e tests (#15886) 2024-07-24 07:15:26 +00:00
Trillium S 2f7cc05cad fix: Update component order, props for Link in user-public-view (#15882) 2024-07-24 12:28:52 +05:30
Keith Williams 84be4dba8d chore: v4.3.2 (#15879)
release.yml / release (push) Canceled after 0s
2024-07-23 16:52:12 +02:00
Peer Richelsen 17655cd1c8 chore: updated feature_request.md template (#15877)
* Update feature_request.md

* Update .github/ISSUE_TEMPLATE/feature_request.md
2024-07-23 15:10:25 +02:00
Amit SharmaandSomay Chauhan 010f5349f6 feat: Link to rescheduled booking from the old one (#15211)
* feat: Link to rescheduled booking from the old one

* add original comment

* use fromReschedule

* fix: type check

* review updates

* Update common.json

* change booking page text

* Delete packages/features/bookings/lib/getRescheduledToBooking.tsx

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
2024-07-23 12:39:50 +00:00
Lauris Skraucis 01dead9a1d refactor: auto-accept membership for v2 org team creator (#15866) 2024-07-23 12:34:33 +00:00
Peer Richelsen 900ea37b43 chore: updated bug_report.md template (#15876)
* Update bug_report.md

* Update .github/ISSUE_TEMPLATE/bug_report.md

* Update .github/ISSUE_TEMPLATE/bug_report.md

* Update .github/ISSUE_TEMPLATE/bug_report.md
2024-07-23 12:24:42 +00:00
Morgan c209c22b98 chore: add cleanup to api auth strategy e2e test apiv2 (#15875) 2024-07-23 12:22:46 +00:00
b8610dc2d1 test: Create 4 test cases to POST Attendees (#15856)
* Create 4 test cases to POST Attendees

* Adjusted commented tips on PR

* Adjusted isAdmin to isSystemWideAdmin

* Removed req.isAdmin

* Delete test-insights.csv

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-23 14:19:36 +02:00
Anik Dhabal BabuandAmit Sharma 28f84d3570 feat: skype app added (#14751)
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-23 09:39:34 +00:00
b46a902a3d feat: api endpoints for calendar settings atom (#15761)
* calendars repository

* add disconnect endpoint

* update platform libraries to include getCalendar fn

* cleanup

* fix typings

* update calendars service to include delete credentials handler

* update typings

* add missing return

* refactor delete calendar credentials handler

* update disconnect endpoint

* fix typing

* fixup

* add handlers to insert and remove selected calendar

* init selected calendars controller

* fix naming

* output response dto for deleting calendar credentials

* fix typing

* fix merge conflicts

* resolve merge conflicts

* fixup

* cleanup

* capitalize controller name

* include selected calendars controller in selected calendars module

* fix type error

* cleanup

* fixup

* add calendars repository

* custom hook for calendar credentials

* fix typing

* cleanup

* take input from query params instead of body in delete request

* custom hook to add selected calendar

* custom hook to remove selected calendar

* better naming

* fix typo

* fixup

* update input for delete calendar query params

* address PR feedback

* add method to check calendar credentials

* abstract logic to check calendar credentials in calendars service

* resolve module errors

* export custom hooks for calendar settings

* e2e for deleting calendar credentials endpoint

* fix typo

* set authorization header to calendars post

* e2e tests for selected calendars controller

* fix output typing

* better error messages

* restructuring

* fix imports

* remove unused not found exception

* fixup! remove unused not found exception

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-07-22 17:26:39 +00:00
Lauris Skraucis de2f51e104 feat: v2 API enable updating event type schedule (#15803)
* refactor: event-types CREATE, UPDATE accept scheduleId and return scheduleId

* chore: enable passing scheduleId to event-types create handler

* chore: use platform-libraries at version 0.0.20

* fix: remove team output schedule

* finish merge main into this branch

* fix: dont allow adding scheduleId to event-type if user does not own schedule
2024-07-22 20:09:54 +03:00
Hariom Balhara 35110892c1 fix: Fix React cal api namespaced usage (#15834)
* fix: Fix React cal api namespaced usage

* Update e2e
2024-07-22 12:01:05 +02:00
Hichem FantarandAnik Dhabal Babu c870df7027 chore: Add transition effects in UI components (#15841)
* chore: Add transition to hover effects in UI components

* chore: Add transition to hover effects in UI components

* Update ToggleGroup component transition

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-22 11:50:44 +02:00
Krishnendu SukumarandAnik Dhabal Babu a4b0fdba7b fix: Text in React Embed Code Becomes Invisible on Selection (#15835)
* fix: #15832

* fix: #15832

* Revert yarn.lock to its state before unintended changes

* Update EmbedTabs.tsx

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-22 09:26:13 +02:00
Punit Kumar Ojha b530d31b35 fix: added fix for email title for issue# 15848 (#15855) 2024-07-22 09:22:08 +02:00
Anik Dhabal Babu a69f9c5fa3 fix: broken ui of availability setup in onboarding (#15854) 2024-07-22 09:20:48 +02:00
73b8569a16 fix: added translations for et (#15762)
* added translations for et

* estonian support

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-22 08:42:07 +02:00
Hariom BalharaandUdit Takkar 5becc95fc4 feat: Add a fresh signup to matching organization after email verification (#15705)
* Support automatically adding user to organization

* Use Repository

* Fix unit test

* Code Improvements

* Fix type error

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-20 12:22:42 +05:30
SuPrabhuandsumantha a74522af3d fix: TimeZone select should show the selected timezone (#15838)
* bugfix - CAL4052 TimeZone select should show the selected timezone

* Pusheding the code for type - check failure

* Updating MenuPosition as absolute

---------

Co-authored-by: sumantha <sumantha-ullasa-prabhu@hpe.com>
2024-07-19 23:49:12 +05:30
Lauris Skraucis 8b23e41954 feat: v2 organizations event types endpoints (#15775)
* fix: event-types input service transform length property

* feat: create organizations event-types controller, service and repository

* feat: enable organizations event types

* fix: create team request user isOrgAdmin check

* refactor: hosts array required in team input and output

* chore: use platform-libraries at version 0.0.19

* tests: test organizations event-types

* feat: enable managed event-types

* chore: fix main merge into this branch

* feat: assign all team members for managed event-types

* refactor: gathering managed event-type users ids

* chore: include assignAllTeamMembers in team event response

* test: assign all team members db event

* chore: delete unused files

* docs: swagger create and update request body objects

* revert: event-types platform-libraries update

* refactor: organisations event-types endpoints accessible by TEAM_ADMIN and up

* fix: managed event types output hosts array empty

* fix: dont allow creating event-type for non-team member

* chore: delete unused variables

* tests: negative tests

* chore: finish main merge
2024-07-19 15:12:05 +00:00
Morgan 94873ea3cb fix: api-auth-strategy tests are e2e apiv2 (#15837) 2024-07-19 14:51:50 +00:00
0c0cac28fd fix: multiple getSchedule requests on email change in booker (#15616)
* fix: multiple getSchedule requests on email change in booker

* fix: use debounce for formEmail

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-19 12:30:41 +00:00
Richard Poelderl 769ca9002e fix(website): fix sitemap by adding prisma env var (#15827)
* update docs for `yarn.lock` management

* fix(website): Add `DATABASE_URL` as required environment variable to `website`
2024-07-19 11:56:37 +00:00
Morgan 03366d2337 feat: api v2 org team memberships endpoints (#15820)
* feat: api v2 org team memberships endpoints

* fixup! feat: api v2 org team memberships endpoints
2024-07-19 11:44:16 +00:00
159fe3a87d fix: system default theme for dashboard and booking page (#15291)
* fix: system default theme

* update

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
2024-07-19 13:42:21 +02:00
Morgan 5b97d3bf52 fix: revert libraries version event-types controller apiv2 (#15831) 2024-07-19 08:50:15 +00:00
Morgan 7cdecf57e0 fix: PATCH /me formatted timezone incorrectly (#15830) 2024-07-19 08:49:59 +00:00
Udit Takkar 1c8d0eda45 chore: update translation (#15818)
* chore: change title and description

* chore: update
2024-07-19 07:40:59 +00:00
7ee3f9409c feat: [CAL-3939] fix org banner (#15557)
* update documentation for proper hostile command

* add OrgBanner component and replace it in ee organization profile settings

* add width and height defaults, with option to override

* updated sizing with image loaded

* fix: add alt to OrgBanner in test

* fix: failing OrgBanner test, added neg test

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-19 05:30:50 +00:00
Keith Williams 0d1dd036eb revert: "chore: removed cal.ai email assistant (#15793)" (#15823) 2024-07-18 11:57:40 +00:00
Omar López 2af27170cd chore: v4.3.1 (#15817)
release.yml / release (push) Canceled after 0s
2024-07-18 13:09:30 +02:00
Joe Au-Yeungandzomars 218f6d6eaf feat: Salesforce - Disable creating new contacts (#15780)
Co-authored-by: zomars <zomars@me.com>
2024-07-18 10:50:42 +00:00
Hariom Balhara f0464e72c7 fix: Check URL (#15815) 2024-07-18 10:20:03 +00:00
Anik Dhabal BabuandPeer Richelsen 2ad3592eb3 chore: Introduce a new class WebAppURL that extends URL (#14982)
* chore: Introduce a new class WebAppURL that extends URL

* update

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-18 07:27:34 +00:00
Syed Ali ShahbazandMorgan Vernay 22fc47f69f feat: V2 Org Memberships (#15578)
* init

* --WIP

* WIP

* ...

* more wip

* doc

* address feedback --WIP

* further requested changes

* fix

* add missing parseIntPipe

* chore: code review comments + e2e tests

* fixup! chore: code review comments + e2e tests

* fixup! fixup! chore: code review comments + e2e tests

* fix: don't allow passing teamId for org membership create/update

* fix: improve docs and DTOs

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-07-18 03:43:20 +00:00
Morgan af3d2e6879 chore: set managed user locale via api v2 (#15794) 2024-07-17 12:56:37 +00:00
Morgan 58e61cb18e fix: findOrgUser use profiles instead of organizationId apiv2 (#15808) 2024-07-17 12:51:42 +00:00
NitinPSingh 9e3f8a2dc0 fix: Add signout button on onboarding screen (#15807)
* fix #15787 created signoutbtn on onboarding screen

* fix: Add signout button on onboarding screen and changes done as per feedback

* small error removed
2024-07-17 14:32:13 +02:00
Udit Takkar e960b8ad10 fix: fetch workflows on the backend (#15796)
* fix: fetch workflows on the router

* chore: remove workflows

* chore: import zod

* chore: move to repository
2024-07-17 07:02:22 +00:00
Peer Richelsen b7bfd77f95 readded border (#15798) 2024-07-16 15:46:40 +00:00
Peer Richelsen 2707adbb7b chore: removed cal.ai email assistant (#15793)
* removed cal-ai

* removed apps/ai
2024-07-16 14:21:00 +00:00
Morgan 5ef1b22c87 fix: remove permission blocking public apiv2 event-types endpoint (#15801) 2024-07-16 14:05:51 +00:00
Mohit YadavandKeith Williams 7e29db7a1b docs(contributing): change fork link (#15792)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-16 14:25:06 +02:00
41cde8e384 feat: API org user scope (#15739)
* Init organizations users GET endpoint

* Fix get users endpoint

* Add create user input

* Add POST to organization user endpoint

* Creating a user make email mandatory

* Move DTOs out of platform types

* Use plainToInstance for data filtering

* POST create user and org membership

* Add email check

* Filter update user call

* Add getOrg decorator to endpoints

* Add update endpoint

* Add delete endpoint

* Fix merge changes to create new org user

* Init tests

* Send org signup email

* Abstract username check

* Implement email service

* WIP E2E test

* Rename methods

* Update update org user DTO

* Remove unused inputs

* chore: add ApiProperty decorator to users class validators

* Type fix

* Update tests

* chore: code review comments

* chore: code review comments

* chore: code review comments

---------

Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-07-16 10:50:46 +00:00
MorganandLauris Skraucis 9fec77e345 fix: creating organization team should add owner (#15779)
* chore: platform libraries versions cleanup api v2

* fix: create membership to created org team apiv2

* chore: add test check owner membership

---------

Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2024-07-16 10:31:29 +00:00
7046f6f80a feat: Fix Proton Calendar with ICS (#15454)
* feat: fix proton calendar with ics

* ics-feed: add note & clarify checkbox

* fix review

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-16 12:08:54 +02:00
Hariom Balhara 3f4a0eff7f Enable embed namespacing again (#15783) 2024-07-16 12:07:37 +02:00
Peer Richelsen d2768c4dc5 chore: Update /ee README.md (#15765)
* Update README.md

* Update README.md
2024-07-16 09:52:12 +00:00
470e0f53d8 feat: allow passing initial data props to calendar connectors (#15760)
* feat: allow passing init data to AvailabilitySettings, OAuthConnect wrappers

* chore: gitignore idea

* chore: minor fixes

* chore: use yarn.lock from main branch

---------

Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-16 09:47:50 +00:00
Lauris Skraucis 003af9a69d chore: specify platform-libraries version for event-types (#15785) 2024-07-16 09:47:37 +00:00
VijayandAmit Sharma 283ca9fc74 fix: #15749 Display availability page as per weekstart setting (#15753)
* update to display availability page as per weekstart setting

* update to accomodate any day

* update for AvailabilityTab

* reusable and tests added

* update test description

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-16 11:31:02 +02:00
Pranav RajveerandAmit Sharma 13ec930a61 fix: filter admin_api if org is present (#15386)
* fix: filter admin_api if org is present

* remove admin_api from developers tab when org is present

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-16 11:30:39 +02:00
Lauris Skraucis cada4a58ac chore: add platform-libraries change log (#15786) 2024-07-16 10:53:26 +02:00
Lauris Skraucis 77519a077c refactor: allow system admins to create event-type without membership (#15774) 2024-07-16 06:08:28 +00:00
Nick Holden 1a5a539106 fix: higher resolution Campsite icon (#15777) 2024-07-16 04:21:34 +00:00
Morgan 539a2ea301 chore: platform libraries versions cleanup api v2 (#15768)
* chore: platform libraries versions cleanup api v2

* fixup! chore: platform libraries versions cleanup api v2
2024-07-15 17:03:15 +00:00
Alex van Andel c8b577fb7a chore: Release v4.3.0 (#15771)
release.yml / release (push) Canceled after 0s
2024-07-15 13:55:09 +00:00
Peer Richelsen 7f97d27c20 chore: Update README.md (#15766)
* Update README.md

* Update README.md

* Update README.md
2024-07-15 13:40:28 +02:00
Morgan 9ff10877e7 chore: fix apiv2 org teams doc (#15746) 2024-07-15 10:27:33 +00:00
Morgan eb0768917c chore: readme platform libraries (#15737)
* chore: readme platform libraries

* fixup! chore: readme platform libraries

* fixup! fixup! chore: readme platform libraries

* Update README.md

* Update README.md
2024-07-15 10:13:41 +00:00
ec65cd6333 fix: icon visibility and add up and down icons (#15543)
* fixed the icons visibility and added up and down icon

* wip: TimeUnitAddonSuffix

* chore: Quick pass for rerender-optimisation

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-15 09:35:48 +00:00
Udit Takkar 563141b295 chore: add check for booking (#15740)
* chore: add check

* chore: missting no shoe
2024-07-15 08:59:29 +00:00
Hariom Balhara 7b24972900 fix: Ensure unique username in an organization (#15730) 2024-07-15 06:11:24 +00:00
Somay Chauhan 6d2ec71df3 add missing getServerSideProps to /event-types (#15731) 2024-07-13 09:51:00 +00:00
492304fe1d feat: BookerEmbed atom (#15689)
* init: booker embed atom

* update booker platform wrapper

* export booker embed for atoms

* update booker embed wrapper

* fixup: refresh url not needed

* rename booker embed

* add global styles

* export not needed

* add booker embed atoms in examples app

* fixup

* fixup! fixup

* fixup! fixup! fixup

---------

Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
Co-authored-by: Morgan Vernay <morgan@cal.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-07-12 11:10:32 +00:00
c7c5cdf133 feat: Campsite conferencing app (#15713)
* feat: Campsite conferencing app

* Improve redirect after OAuth flow

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-07-11 14:20:31 +00:00
Keith Williams 0dd3b25624 chore: v4.2.5 (#15735)
release.yml / release (push) Canceled after 0s
2024-07-11 15:03:37 +02:00
Morgan a633e35c66 fix: api v2 unit tests (#15733)
* fix: permissions guard spec

* fix: api auth strategy spec
2024-07-11 11:27:40 +00:00
sean-brydon c618e69d8e fix: event type back button (#15722) 2024-07-11 10:44:37 +00:00
Morgan 844dd7e3af fix: remove console.log and tsignore apiv2 (#15732) 2024-07-11 10:40:11 +00:00
Morgan b6c512e5d7 fix: api-v2 controllers e2e tests (#15724)
* fix: bookings controllers e2e tests

* fixup! fix: bookings controllers e2e tests

* fix: calendars  controllers e2e tests

* chore: move org teams controller files into folder

* fix: gcal controller e2e tests

* fix: oauth-clients.controller.e2e-spec

* fix: calendars  controllers e2e tests

* fix: gcal controller e2e tests

* fix: oauth-flow.controller.e2e-spec.ts
2024-07-11 13:14:45 +03:00
Ankit SinghandKeith Williams 6e0035689a fix: Text in Embed Code Visibility Fixed (#15711)
* Fix:#15707

* bg-subtle fixed

* Update packages/features/embed/lib/EmbedTabs.tsx

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-10 16:18:31 +00:00
58540af9ce chore: sort calendar crendentials in event manager (#15448)
* chore_sort_calendar_credentials_in_desc_for_reliability

* chore: create reusable function

* refactor: rename functions

* Update EventManager.ts

Variable name change

---------

Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-10 16:16:26 +00:00
Morgan 21f30fc47a chore: cache org guard and fix roles guard apiv2 (#15719) 2024-07-10 15:46:04 +00:00
Amit Sharma 92845d3adc Revert "fix: Autodetection of time zone only updated default time zone (#15392)" (#15720)
This reverts commit 285d1ec4c9.
2024-07-10 15:28:41 +00:00
8ee27d3997 feat: add POST end point to mark calls as no-shows (#15690)
* feat: add POST end point to mark calls as no-shows

* fix: type

* feat: add v2 endpoint

* wip

* chore: type err

* chore: add permission

* chore: remove packae

* chore: remove v1 API endpoint

* fix: endpoint

* feat: add new version

* chore: change route

* chore: error msg

* fix: message

* fix: type err

* chore: output

* fix: type err

* chore: bump libraries to 0.0.17

* chore: add noShowHost

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-07-10 13:01:10 +00:00
Morgan 1263519973 chore: add i18n to atoms (#15698)
* chore: add i18n to atoms

* fixup! chore: add i18n to atoms

* fixup! fixup! chore: add i18n to atoms

* fixup! Merge branch 'platform-atoms-i18n' of github.com:calcom/cal.com into platform-atoms-i18n

* fixup! Merge branch 'main' into platform-atoms-i18n
2024-07-10 12:34:41 +00:00
caf7943c91 fix: set conferencing apps as default (#15376)
* fix: set conferencing apps as default

* fix in other oauth conferencing apps

* creating default event-types should set the user's default location

* Update getDefaultLocations.ts

* fix: unit test

* default install only for first oauth app

* fix: type-check

* update default on success

* fix: pass callback prop

* chore: code cleanup

* Fix condition

* no longer needed

- as we reverted https://github.com/calcom/cal.com/commit/20fdb50081ae684723a5af78436068c0b09509a4

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-07-10 11:00:59 +00:00
Somay ChauhanandHariom Balhara fed3987028 fix: disable google-meet in user onboarding if google-calendar is not installed (#15702)
* fix: disable google-meet in user onboarding if google-calendar is not installed

* add tooltip

* fix: type check

* fix: type check for tooltip

- allowed `ReactNode` for tooltip content prop

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-10 10:34:14 +00:00
Hariom Balhara ad61d48706 fix: Rescheduling email when there is broken calendar integration (#15669) 2024-07-10 10:32:56 +01:00
Omar López aa20b9248a chore: v4.2.4 (#15703)
release.yml / release (push) Canceled after 0s
2024-07-09 14:09:04 +00:00
Keith Williams fa40f1ead5 fix: 404s becoming 500s (#15696) 2024-07-09 12:35:34 +02:00
Morgan 86676eb8af fix: delete reserved slot on booker unmount (#15700) 2024-07-09 10:08:06 +00:00
Morgan 3f9d1c1abf chore: add caching apiv2 roles guard (#15694) 2024-07-09 09:39:06 +00:00
5f8d090d3c refactor: handleNewBooking #3 (#15612)
* refactor: handleNewBooking #3

* fix: type error

* fix: type err

* fix: type err

* refactor: create booking factor

* chore: refactor availusers

* chore: type err

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-08 18:18:36 +00:00
4a2a2d165b fix: incorrect booking seats full error on collective seated event (#15602)
* fix: wrong booking seats full error on collective event

* test: verify seat availability calculation based on booked seats, not total attendees

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-08 18:02:02 +00:00
Martin SioneandJoe Au-Yeung 263c0ac7ef fix: Update stripe customer email when a user updates cal.com email (#15299)
* fix: Update stripe customer email when a user updates cal.com email

* Only update email on stripe if stripe is enabled and if there is a stripeAccountId already associated with the user

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-07-08 18:00:51 +00:00
f1b4d7d9e0 feat: app install flow followup (#15193)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
2024-07-08 19:15:57 +02:00
Anik Dhabal BabuandKeith Williams 7d8cb7d6bb fix (#15465)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-08 14:17:56 +00:00
Hariom BalharaandKeith Williams 5ff7e65d64 fix: Broken navigation after booking creation in some cases (#15664)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-07-08 13:56:58 +00:00
5bbc025494 fix: 15636 Sort order of team event types incorrect (#15660)
* Fix:15636 event types sort order on team page

* fix type-check

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-08 14:31:27 +01:00
sean-brydon 59bffa075f remove (#15687) 2024-07-08 12:31:15 +00:00
c4104af8a2 feat: org-wide workflows (#15083)
* add org to create workflow button

* add save button for testing in dev mode

* add select all to multi select dropdown

* fix select all

* pass isOrg to WorkflowDetailsPage

* add checkbox to apply to all including future

* fix count text on select

* WIP schema

* shows teams in dropdown

* add team option to UI

* [WIP] refactor for update handler

* filter out "all" from activeOn

* fix type error

* create more helper functions

* create reusable function for scheduling all booking reminders

* finish update workflows for orgs (without testing)

* fix isAuthorized check for orgs

* fix getting newActiveOn

* move all helper functions to util file

* more code clean up

* fix deleting reminders for org workflows

* fix adding and editing steps

* update form data when workflow is saved

* code clean up

* fixing creating/deleting reminders when user is member of several teams

* fix setting activeOn for teams in useffect

* add on delete cascade

* make multiSelectSchenbox searchable

* set activeOn correctly when checkbox true

* WIP

* fix type errors in MultiSelectCheckboxes

* implemented scheduling org-wide workfow notifications (not yet tested)

* fix type errors

* type error

* add missing changes from merge conflict

* remote not needed include statement

* fix type errors

* code clean up + some todo comments

* support org workflows for cancelled workflows

* delete reminders from removed members

* remove reminders if isActiveOnAll is turned off

* fix unti test and type error

* code clean up

* create basis for testing to book org team event

* create org workflow with active team

* fix getting active org workflows on team + test setup

* fix creating workflow step for tests

* fix first org test

* add test for user event type with org workflow active

* use deleteAllWorkflowReminders everywhere

* add test for deleteRemindersFromRemovedActiveOn

* fix type errors

* make all tests pass

* fix type error

* fix getSchedule test

* code clean up

* add missing import

* fix type error

* fix tests

* code clean up

* fix imports

* update reminders when trigger was changed

* check for teamId before userId in reminderScheduler

* move getOrgIdFromMemberOrTeamId to different folder

* code clean up

* fix tests

* test setup for scheduleBookingReminders

* fix typo

* add tests for scheduleBookingReminders

* fix prisma default import

* fix workflowStep type

* add scheduleBookingReminders test for sms

* return dummy sid for scheduleSMS testMode

* clean up + fixes

* add lost changes from merge

* get teamId and userId from incoming evt object

* removing not needed select

* add org support for scheduleMandatory email reminder

* add other teams to dropdown

* move getAllWorkflows to seperate file and call it in parent function

* include org wide workflows in createNewSeat

* some fixes + code clean up

* add new team to select text count when including future teams is checked

* fix upsert and remove sms reminder field

* correctly update activeOn if 'including future ...' is enabled

* list active Org workflows in event workflows settings

* fix sms reminder field in all handlers

* add helper function to check if step was edited

* fix active on badge on workflow

* fix type error

* fix double reminders

* add teamId: null for userWorkflow query

* fix activeOnAll with managed event types

* add missing teamId in getAllWorkflows

* add a dafaut to prisma param

* fix managed event types on select all user workflows

* code clean up

* better variable name

* small fixes in update handler

* fix test name to match function name

* add info badge

* fix workflow count in event type settings

* fix getting bookings from children manged event types

* delete reminders when user is not part of any time no more

* implement feedback

* fix disbale workflow in event type settings

* fix remove member

* create new function getAllWorkflowsFromEventType

* add some removed code

* use promise.allSettled when deleting workflow reminders

* create new function deleteWorkflowRemindersOfRemovedMember.ts

* fix userId param

* delete org worklfows when team is disbanded

* don't trigger active on all workflow if not part of any team

* fix active on count badge

* add test for deleteWorkflowRemidnersOfRemovedMember

* trigger workflow also if not member of any subteam

* fix failing test

* remove unused code

* use testId for go back button

* fixes for managed event types & activateEventTypeHandler

* code clean up

* don't activate workflow on locked managed event type

* fix type error

* type error

* more type fixes

* feedback

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-08 17:44:11 +05:30
Rajiv SahalandRajiv Sahal dca2922ef8 fix: booker atom types (#15685)
Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
2024-07-08 12:00:55 +00:00
MOHD RAZAandAnik Dhabal Babu 8b5ef68652 fix: datePicker skeleton issue solved (#15675)
* datePicker skeleton issue solved

* Update

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-08 17:15:22 +05:30
RushikeshandAnik Dhabal Babu 5b2688dda0 fix: correct message and translation key (#15657)
* fix event_type_requres_google_cal  to requires_google_calendar

* revert the changes

* redirect to google calendar page

* add translation key for "requires_google_calendar_to_install"

* use key and add correct message

* use key

* fix

* revert

* correct message

* refactor event type steup tab UI

* small fix

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-05 14:35:08 +05:30
Udit TakkarandHariom 884ccc9b48 feat: RECORDING_TRANSCRIPTION_GENERATED webhook (#15589)
* feat: RECORDING_TRANSCRIPTION_READY webhook

* refactor: split into diff files and restructuring

* feat: finish batch processor finished

* chore: types

* fix: type error

* chore: change name of triggger

* test: add unit test

* fix: type

* fix: type err

* chore: test

* feat: add booking reference repository and other imp

* chore: log

* fix: import

* chore: type error

* fix: update test

* fix: test

* fix: test

* fix: add timeout

* chore: move beforeEach

* Mock missing env variables to test. These vars are not set in CI

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-07-05 07:39:23 +00:00
7d06d9f865 chore: remove personal branding if in an org and use org branding (#14872)
* chore: remove personal branding if in an org and use org branding

* fix: minor fix

* Update SettingsLayout.tsx

* Update SettingsLayout.tsx

* Update profile.tsx

* some fixes and e2e test

* fix: e2e test

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-07-05 00:35:46 +05:30
785ab8b74a feat: v1 API - add sort option on GET /bookings (#15374)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-07-04 06:11:47 +00:00
557498b80d feat: embed: remove "Preview" tab and instead split code/preview layout 50/50 vertically (#15492)
* feat: embed: remove "Preview" tab and instead split code/preview layout 50/50 vertically

* fix: use single preview tab

* fix e2e

* add border radius

* final e2e fix

* fix: don't show preview tab component

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-04 09:54:35 +05:30
Rajiv SahalandRajiv Sahal c3a532cff0 fix: platform seed script (#15617)
Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
2024-07-03 15:31:41 +00:00
ab5d4f1d03 chore: update formbricks-js to newest version (#15146)
* chore: update formbricks-js to newest version

* fix: Regenerate yarn.lock with right deps

* update formbricks-js version to 2.1

* Update yarn.lock

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-03 17:27:39 +02:00
VijayandAmit Sharma ae79154b83 fix: assignAllTeamMembers field wrongly set in db (#15654)
* fix for assignAllTeamMembers update

* update for true to false scenario

* Update packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>

---------

Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
2024-07-03 17:23:14 +02:00
Anik Dhabal BabuandUdit Takkar e33c676ec5 fix: toast message is not visble and some refactor (#15649)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-07-03 18:28:48 +05:30
8cd9285cc6 fix: Remove/Disable Duplicate button in Read Only mode of an event-type (#15511)
* fix: Remove/Disable Duplicate button in Read Only mode of an event-type

* fix: use readonly variable

* fix: hiding for smaller devices

* update test cases

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-03 15:23:22 +05:30
Udit Takkar 4f76d4468e fix: passing booking id in the payload (#15642) 2024-07-03 07:25:14 +00:00
RushikeshandAnik Dhabal Babu 92bf1ee3c7 refactor: remove duplicate object key in common.json (#15626)
* remove duplicate object key in common.json

* Update common.json

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-02 08:42:01 +00:00
e9aec23fbe fix: typo in german translation (#15637)
* fix: #11213, #11215

* Use default colour

* Fix typo in german translation

* fix_the_conflict

---------

Co-authored-by: Siddharth Movaliya <siddraj1992@gmail.com>
Co-authored-by: Sean Brydon <sean@cal.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
2024-07-02 11:33:23 +05:30
a6a60969df fix: Redirect URL with query params (#13912)
* Fix for  issue #13899 Redirect URL

* Update bookingSuccessRedirect.ts

* Update bookingSuccessRedirect.ts

* fix: url params

* chore: remove unused

* chore: remove comment

* Update

---------

Co-authored-by: Vikas Verma <inboxvikasverma@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-01 22:18:47 +00:00
Atchyut Preetham PulavarthiandAmit91848 e9d828639a fix(@cal.com/web): update debounce logic to work correctly and not show non-premium usernames as premium (#15629)
* fix(@cal.com/web): update debounce logic to work correctly and not show non-premium usernames as premium

- removes redundant useMemo hook from UsernameTextfield.tsx & PremiumTextField.tsx
- cancles the debounceAPICall on every unmount of UsernameTextfield.tsx & PremiumTextfield.tsx via useEffect cleanup function

Fixes #15628

* fix(@cal.com/web): update the debounce logic to use the pre-existing useDebounce hook

- update the debounce logic in UsernameTextField.tsx & PremiumTextField.tsx to use the pre-existing useDebounce hook instead of loadsh debounce
- update the debounce delay to 600ms to be consistent with the signup.tsx behavior

Fixes #15628

* fix: signup debounce not working properly

---------

Co-authored-by: Amit91848 <samit91848@gmail.com>
2024-07-01 15:45:09 +00:00
Amit SharmaandAnik Dhabal Babu 726c938b6d fix: make toast consisten with brand colour (#15638)
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2024-07-01 15:43:27 +00:00
Rushikesh b43aaaccc2 fix: use correct object keys (#15631)
* use connected_google_calendar translation key

* use requires_google_calendar translation key

* Remove unused ExistingGoogleCal component
2024-07-01 11:28:14 +00:00
Balthasar Huber 0b5f828307 fix: Add fromRedirectOfNonOrgLink in getServerSideProps of private links (#15306) 2024-06-29 09:17:25 +00:00
Anik Dhabal BabuandSomay Chauhan 3807e339ce fix: Provide event rescheduling option before the event end time (#15150)
* feat: Provide event rescheduling option

* update

* update

* add checker function

* refactor and update

* update

* fix type error

* Update bookings-single-view.tsx

---------

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
2024-06-28 19:09:33 +05:30
Hariom Balhara ececef96e5 feat: Support running webapp and embed-core playground on HTTPS. It allows to test SameSite cookie attribute easily in embed (#15272)
* feat: Support running webapp and embed-core playground on HTTPS. It allows to test SameSite attribute easily in embed

* Update yarn.lock

* feat: Support running webapp and embed-core playground on HTTPS. It allows to test SameSite attribute easily in embed

* Update yarn.lock

* Fix git-setup.sh

* Fix yarn.lock
2024-06-28 13:06:59 +01:00
sean-brydonandKeith Williams 50338dcd61 fix: Update pr.yml (#15611)
* Update pr.yml

* Added the types back so PR update triggers when labeled

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-28 10:02:50 +00:00
Amit Sharmaandsean-brydon 285d1ec4c9 fix: Autodetection of time zone only updated default time zone (#15392)
* fix: Autodetection of time zone only updated default time zone

* fix: description text

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-06-28 10:12:33 +01:00
Udit Takkar c458b297ae fix: backward compatibility for old cal rooms (#15609)
* fix: record icon for old rooms

* chore: change function name
2024-06-28 09:42:38 +01:00
Udit TakkarandKeith Williams a52f7efe88 refactor: move functions to separate files (#15590)
* refactor: move functions to separate files

* chore: remove inline function

* chore: type error

* fix: type error

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-27 22:05:11 +00:00
3e10e2db36 feat: adds env variable for email sender name (#15598)
* feat: adds env variable for email sender name

* update env declaration file

---------

Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-27 18:25:13 -03:00
Omar López dfbcf4742f v4.2.3 (#15604)
release.yml / release (push) Canceled after 0s
2024-06-27 22:52:12 +02:00
3a2a70d83a fix: getPublicEventType of NonOrgUser fetch OrgUser event-type of same username (#15587)
* fix: getPublicEventType of NonOrgUser fetch OrgUser event-type with same username

* Fix the test that was supposed to verify the scenario

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2024-06-27 20:32:19 +00:00
3948b8d203 feat: booking no show webhook (#15502)
* WIP

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

* WIP

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

* WIP

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

* Type fixes

* Update webhook.e2e.ts

* Update noShow.handler.ts

* Log failed results

* Updates tests

* Show generic error on frontend.

* test: add basic webhook service test

* fix: webhook end to end test

* fix: type error

* fix: test

---------

Signed-off-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-06-27 20:12:09 +00:00
Keith Williams 6207afdf03 fix: PR update E2E tests problems (#15603) 2024-06-27 19:12:59 +00:00
Lauris Skraucis 52c7a89785 feat: organization schedules endpoints (#15584)
* feat: organization schedules endpoints

* refactor: add IsUserInOrg guard

* refactor: separate schedules controller folder

* tests

* chore: add roles guard and test it

* refactor: dont use Promise.all

* refactor: rely on profiles instead of user.organizationId

* feat: add pagination to getOrganizationSchedules

* refactor: default skip=0 and take=250

* chore: regenerate swagger doc
2024-06-27 19:04:26 +00:00
Hariom BalharaandAlex van Andel dda4ce5338 fix-moving-a-team-with-just-owner (#15595)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-06-27 17:57:48 +00:00
a02747fc52 fix: update z-index for intercom app launcher (#15516)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-06-27 17:57:29 +00:00
Morgan 03311def07 fix: update version apiv2 platform libraries for createNewUsersConnectToOrgIfExists (#15601) 2024-06-27 17:14:00 +00:00
5e382ffd21 feat: apple calendar connect atom (#15510)
* init apple calendar service

* update apple calendar service logic

* update typing for CalendarApp interface

* add id and type for apple calendar

* fixup: logic for apple calendar service

* update logic for checkIfCalendarConnected function

* add apple calendar service to calendars module

* fixup

* update calendars to include cases for apple calendar

* fix imports

* init frontend for apple calendar connect

* add apple connect atom to connect atom

* fixup

* fixup

* add alias for latest version of platform library

* fix import paths

* custom hook for saving apple calendar credentials

* add apple calendar to examples app

* add interface for credentials syncing calendars

* bring calendars controller to orginal state

* refactor

* update custom hook to sync credentials

* update atom

* resolve merge conflicts

* add endpoint for syncing calendar credentials

* fixup

* update atom

* update useCheck to invalidate queries onSubmit

* fix typo

* fixup

* remove unused tokens respository

* rename controller

* cleanup

* fix: reset react queryClient on access token change

---------

Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Morgan Vernay <morgan@cal.com>
2024-06-27 15:51:27 +00:00
Udit Takkar 0e0dfcdd88 fix: reduce cal recording bitrate (#15588)
* fix: reduce cal recording bitrate

* chore: bitrate

* chore: add enable recording ui

* fix: genereate meeting token

* chore: add wait for recording
2024-06-27 15:43:20 +00:00
Morgan e260809b08 feat: organizations teams endpoints apiv2 (#15586)
* feat: organizations teams endpoints apiv2

* fixup! Merge branch 'main' into feat-api-v2-org-teams-endpoints

* chore: add roles guard to endpoints

* add tests

* fixup! Merge branch 'main' into feat-api-v2-org-teams-endpoints

* fixup! fixup! Merge branch 'main' into feat-api-v2-org-teams-endpoints

* fixup! fixup! fixup! Merge branch 'main' into feat-api-v2-org-teams-endpoints
2024-06-27 18:04:37 +02:00
sean-brydonandHariom Balhara 835443a0d1 fix: cancel sub when movingTeamToOrg (#15594)
* fix: cancel sub when movingTeamToOrg

* Cleanup code a little

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

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

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

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

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-06-27 10:38:42 +00:00
Crowdin Bot 48842e72a6 New Crowdin translations by Github Action 2024-06-27 09:58:07 +00:00
Morgan ff8931f4a0 fix: path /me endpoint should capitalize timezone apiv2 (#15593) 2024-06-27 09:41:00 +00:00
Hariom Balhara 6670bbc1d7 fix: Error in team members migration during org onboarding (#15349)
* fix: Error in team members migration during org onboarding

* Add invitationMemberHandler tests

* Add unit tests

* Improve tests and refactor

* Improve tests and refactor

* Fix type issue

* Fix createNewUsersConnectToOrgIfExists args
2024-06-27 00:53:55 +00:00
Alex van Andel 750676f91a chore: Add dynamic import and trigger captureException (#15583) 2024-06-26 22:32:26 +00:00
Sangram Bahadur db60f8faed setting background color to red for remove button in profile section (#15585) 2024-06-26 20:52:20 +00:00
Somay Chauhan 2b3cb20fdf fix: office365video expiry_date (#15561)
* fix: office#65Video expiry_date

* Revert "fix: office#65Video expiry_date"

This reverts commit 6142287805b5c6a40e746126735745a5617334c7.

* Change logger.debug to logger.error for better visibility of all log cases
2024-06-26 18:12:36 +00:00
Morgan 7f1d45bb5d chore: minimum required roles guard api-v2 (#15576)
* chore: minimum required roles guard api-v2

* fixup! chore: minimum required roles guard api-v2

* fixup! fixup! chore: minimum required roles guard api-v2

* fixup! Merge branch 'chore-roles-guard-api-v2' of github.com:calcom/cal.com into chore-roles-guard-api-v2

* fixup! Merge branch 'chore-roles-guard-api-v2' of github.com:calcom/cal.com into chore-roles-guard-api-v2
2024-06-26 18:00:48 +00:00
Lauris Skraucis 6632c26063 fix: exclude web wrappers from dist (#15572) 2024-06-26 14:41:37 +00:00
Keith Williams 97a3189c12 chore: Update PR template (#15581) 2024-06-26 14:29:26 +00:00
Udit Takkar d25595b546 feat: add instant meeting expiry input (#15555)
* feat: add instant meeting expiry input

* fix: type err

* chore: remove type

* chore: update label

* chore: update column name
2024-06-26 13:56:05 +00:00
Keith Williams a28eb63d61 chore: Removed unwanted CI changes that made it into main (#15580) 2024-06-26 13:24:38 +00:00
Omar Lópezandsean-brydon 4f944ec12d hotfix: team billing on batch operations (#15570)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-06-26 12:06:07 +00:00
Keith Williams e33930e5c0 chore: Cache timezones by calcom version (#15556)
* chore: Cache timezones by calcom version

* Fix types
2024-06-26 11:43:02 +00:00
Lauris Skraucis 9613b9da6d chore: IsUserInOrd guard (#15579) 2024-06-26 11:21:39 +00:00
Morgan ec755b142c chore: IsTeamInOrg guard and decorator apiv2 (#15567) 2024-06-26 07:32:49 +00:00
Alex van Andel d431607125 chore: Release 4.2.2 (#15568)
release.yml / release (push) Canceled after 0s
2024-06-25 21:45:42 +00:00
Joe Au-Yeung 72253021ef Include business days plug and window types (#15569) 2024-06-25 16:23:26 +00:00
Morgan be0cc31d63 fix: IsOrgGuard apiv2 casing (#15566) 2024-06-25 14:13:07 +00:00
sean-brydon 38e67ccc79 Use flex box properly and use description as intended (#15565) 2024-06-25 12:52:53 +00:00
Hariom Balhara 3fe26f19ff Fix git-setup.sh (#15560) 2024-06-25 11:45:33 +00:00
Crowdin Bot f38f98a0b6 New Crowdin translations by Github Action 2024-06-25 11:24:53 +00:00
Morgan b4d5f511b5 chore: IsOrg guard api v2 (#15563)
* chore: is org guard api v2

* fixup! chore: is org guard api v2

* fixup! fixup! chore: is org guard api v2
2024-06-25 10:34:00 +00:00
86fc5161b6 refactor: loadUsers and move to separate file (#15532)
* refactor: loadUsers and move to separate file

* fix: improvement

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
2024-06-25 07:02:45 +00:00
Alex van Andel dbf4be27ea fix: SAML: invalid_code issue, missing user error signing up (#15522)
* fix: Error thrown due to missing user

* chore: Remove unnecessary logs

* fix: Remove PageWrapper in order to prevent double signIn

* Change profile typing to optional

* further type fix

* further type fix..
2024-06-24 14:53:23 +00:00
7b69a6ab9c feat: add monthly annual billing org form (#15520)
* Add annuall billing options

* added payment tests

* Add conitional + tests

* Fix types + use correct ID

* Fix type error

* fix type check

* Assign default to monthly billing period

* cleanup

* chore: rename fn and params

* Prevent ability to deselect toggle group

* Calculate yearly price

* Fix TS error

* revese billingPeriod

* reformat how we handle billingPeriod

* Improve type

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-06-24 14:03:27 +00:00
f897c680a4 fix: zoom shows up as location when expired instead of cal (#15153)
* fix:location doesn't match with meeting url when defaults to calvideo on app credentials expire

* revert temporary logs

* chore: use DailyLocationType

* tests:modify exisiting tests

---------

Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-06-24 12:22:12 +00:00
ac05693694 fix: Transaltion for Dashboard Theme box is not available in all languages #15480 (#15484)
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-06-24 11:16:35 +00:00
Alex van AndelandKeith Williams 3174b55e21 chore: Attempt reduction of Sentry size by treeshaking tracing (#15527)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-24 11:16:19 +00:00
Lauris SkraucisandKeith Williams 08f4a48774 fix: v2 event-types versioning (#15549)
* Added a log for pull_request

* Added labels logging

* Using labels straight from event PR object

* Converting to JSON

* Switching to use payload

* Fixed issue with undefined pr

* Fixed non-mapping issue

* Removed the types

* Added another cache key segment using the head commit sha

* Added separate workflow for labeled action

* Fixed syntax error

* Fixing payload issue

* Added log

* logging full object

* Put e2e back in the names to help find them

* Limited logging

* fix: v2 even-types versioning

* test: old v2 even-types request with VERSION_2024_06_11

* test: old v2 even-types request with VERSION_2024_04_15

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-24 10:43:58 +00:00
Keith Williams 928dd49965 chore: Remove check for labeled action (#15547) 2024-06-24 06:44:37 +00:00
Keith Williams 47a8ba1eaa chore: streamline ready-for-e2e label check for PRs (#15545)
* Added a log for pull_request

* Added labels logging

* Using labels straight from event PR object

* Converting to JSON

* Switching to use payload

* Fixed issue with undefined pr

* Fixed non-mapping issue

* Removed the types

* Removed issue with run-jobs

* Put back the types
2024-06-23 15:38:59 +00:00
Keith Williams c4e78c213b chore: Add GH actions test branch (#15541)
* chore: Add GH actions test branch

* Added a log of the pull_request object

* Removed log

* Update pr.yml
2024-06-22 17:59:15 -03:00
Keith Williams a31cf9963d chore: Make ready-for-e2e label check more robust (#15536)
* chore: Make ready-for-e2e label check more robust

* fixed owner issue

* Try that again

* multi-step check for PR

* added catch

* Using the output from changes

* more fixes

* logging the sha

* Attemptig to fix the sha problem

* Cleanup

* Cleanup

* Testing with the same sha reference as checkout

* Reverted back

* cleanup
2024-06-22 14:04:37 +00:00
Keith Williams 7ff255d1e7 fix: New CI workflow flaws in detecting the ready-for-e2e label on PRs (#15534)
* logging the event

* Added logging

* Trying a different way to reference the event names

* Trying to fix string issues

* Clean up
2024-06-22 07:59:40 +00:00
Keith WilliamsandJoe Au-Yeung 4a88051531 chore: Only run E2E suite when ready-for-e2e label added (#15529)
* chore: Improve CI workflow

* Made a new pre-merge flow that runs the builds and e2e

* Refactored back into 1 file

* Adding another condition for checking the ready-for-e2e

* syntax

* More syntax

* Change e2e-required to only run when ready so it doesn't fail

* A different attempt at getting the ready-for-e2e label

* Supporting PRs and workflow_dispatch

* logging the branch

* hardcoding to test

* Trying the rest call

* Added logging

* Check for the label in the same step

* More logging

* Added job-level output

* Reverted back to 'true'

* Moved the e2e required check into the main required check

* More logging

* Peeling the branch off of the ref

* Fix the ref

* Trying a separate workflow for labeling

* Updated name for the labeled file

* Checking label events to avoid unnecessary runs

* Got rid of the extra check to see if required should run

* Upgraded old actions running Node 16

* Updated the pre-reqs for running jobs

* Removed the dependency of API builds before starting E2E suites

* Removed extra production build used by analyze job

* Fixed the run-e2e check

* Using the pull request object when available

* Checking for null PR

* Added check for no event

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-06-22 00:52:27 +00:00
VijayandJoe Au-Yeung 765b0b44f8 fix: #15487 select arbitary start time (#15523)
* update for any startdate

* update end time if less than start

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-06-21 18:25:57 +00:00
Amit SharmaandUdit Takkar 10f8b40348 fix: Webex app not working (#15485)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-06-21 18:14:15 +00:00
c9a9f70801 fix: create instant meeting error if DAILY_SCALE_PLAN is false (#15508)
* fix: create instant meeting error due to enable_recording

* fix: check scale plan

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-06-21 14:45:17 +00:00
Alex van Andel c2d7054ec4 fix: isChildEvent issue + loader is shown forever (#15524) 2024-06-21 13:07:46 +00:00
Pranav Rajveer 89ac9a3d31 fix: Dashboard theme language (#15486) 2024-06-21 11:46:14 +00:00
7d9c7a8c22 feat: Add Hungarian support (#14484)
* add: Hungarian support

* Update next-i18next.config.js

* Fix test

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-06-21 11:29:40 +00:00
Lauris Skraucis 48e7efccb2 fix: AvailabilitySettings atom closing override window button (#15518)
* fix: close override window button

* fix: test
2024-06-21 11:06:47 +00:00
Lauris SkraucisandMorgan 0072aba55d refactor: v2 event-types (#15457)
* refactor: UserAvatarGroupWithOrg.tsx remove unecessary profile prop

* chore: define event-type types in platform/types

* feat: event-type transformes in lib and re-exported through platform-libraries

* chore: define user types in platform/types

* chore: add users endpoint to platform-constants

* refactor: app-store use narrowed down booker event type

* chore: version old event-types module

* fix: old event-types e2e test

* fix: libraries add missing export

* fix: reset libraries version to 0

* feat: new event-types module and users endpoints

* feat: make booker atom work with v2 event-types

* updating event type

* refactor: remove guard for get event-types

* refactor: move private hook to public

* Revert "refactor: remove guard for get event-types"

This reverts commit d41204069f1d5bb1305b388ce53399f9175f4249.

* Revert "refactor: move private hook to public"

This reverts commit 09322e8fba102e57104959f79ed6bfa9a677dc9d.

* refactor: get by username and slug in /event-types

* remove console log

* feat: locations handle displayEventPublicly

* test e2e event-types

* revert: user output from types

* refactor: event-types hooks have private and public folders

* refactor: require event-type.slug in input

* refactor: demand that all labels of booking fields are unique

* refactor: remove unused import

* refactor: only have email and name by default in booker

* refactor: add booking field slug

* fix: display event-type location publicly / hide it in booker

* fix: display event-type location publicly / hide it in booker

* fix: packages/lib tests

* fix: typescript in e2e test

* fix: dynamic event types input

* refactor: use IsIn instead of IsEnum

* refactor: simplify getEventTypes

* fix: use ApiAuthGuard instead of AccessTokenGuard

* chore: export more stuff from libraries

* refactor: SchedulingTypeEnum and Type

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-06-21 10:49:12 +00:00
Vedant JainandPeer Richelsen c7a96e1b43 [CAL-3940] randomize Tip Sidebar (#15469)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2024-06-21 09:49:43 +00:00
Morgan 0288ea931f fix: license key env type api-v2 (#15519) 2024-06-21 09:04:43 +00:00
sean-brydonandJoe Au-Yeung c958b6017a fix: modal logic for assignment reminder (#15488)
* fix: modal logic for assignemtn reminder

* Fix: joe feedback

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-06-21 07:48:14 +00:00
Keith Williams ffe03be018 chore: v4.2.1 (#15514)
release.yml / release (push) Canceled after 0s
2024-06-20 20:05:55 +00:00
Lauris Skraucis f760a8ac26 refactor: only check platform billing for platforms (#15507) 2024-06-20 10:27:42 +00:00
smitgolandHariom d0a7eb12a0 fix: your name translation (#14863)
* your name translation fixed

* do translation outside Components as that is needed by all components. Allows the code to stay DRY

---------

Co-authored-by: Hariom <hariombalhara@gmail.com>
2024-06-20 08:39:21 +00:00
Morgan 9a473d5b55 chore: auth on api-v2 with api-key (#15455)
* chore: enable apiv2 auth with api keys

* chore: enable apiv2 auth with api keys

* chore: enable apiv2 auth with api keys

* chore: enable apiv2 auth with api keys

* chore: enable apiv2 auth with api keys

* fixup! chore: enable apiv2 auth with api keys

* fixup! Merge branch 'chore-apiv2-auth-api-key' of github.com:calcom/cal.com into chore-apiv2-auth-api-key

* fixup! fixup! Merge branch 'chore-apiv2-auth-api-key' of github.com:calcom/cal.com into chore-apiv2-auth-api-key

* fixup! fixup! fixup! Merge branch 'chore-apiv2-auth-api-key' of github.com:calcom/cal.com into chore-apiv2-auth-api-key
2024-06-19 16:00:22 +00:00
sean-brydonandKeith Williams c5a83f4df2 fix: correctly assign status code on rate limit error (#15435)
* set status code

* Add tests

* Update apps/api/v1/lib/helpers/rateLimitApiKey.test.ts

* Update apps/api/v1/lib/helpers/rateLimitApiKey.test.ts

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-06-19 15:03:28 +00:00
sean-brydon 3e175dbca3 Remove comment (#15493) 2024-06-19 14:17:32 +00:00
762b995172 fix: event type uses calvideo as location instead of user default (#15442)
* fix_event_type_created_with_calvideo_instead_of_user_default_conferencing_app

* test: add tests for getDefaultLocation

---------

Co-authored-by: Shaik-Sirajuddin <sirajudddinshaik30@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-06-19 14:00:06 +00:00
d2d7453962 feat: add date overrides to availability settings atom (#15459)
* remove console.log

* fixup: logic to convert overrides was not calling functions

* refactor handleSubmit function

* add portal to platform dialog component

* cleanup

* add props to enable and disable date overrides

* show date overrides for availability settings atom

* fix: date overrides formatting for api request

---------

Co-authored-by: Rajiv Sahal <rajivsahal@Rajivs-MacBook-Pro.local>
Co-authored-by: supalarry <laurisskraucis@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-06-19 12:19:25 +00:00
1028 changed files with 48523 additions and 10552 deletions
+4 -4
View File
@@ -15,15 +15,14 @@
# - You can not repackage or sell the codebase
# - Acquire a commercial license to remove these terms by visiting: cal.com/sales
#
# To enable enterprise-only features, as an admin, go to /auth/setup to select your license and follow
# instructions. This environment variable is deprecated although still supported for backward compatibility.
# @see https://console.cal.com
# To enable enterprise-only features please add your envrionment variable to the .env file then make your way to /auth/setup to select your license and follow instructions.
CALCOM_LICENSE_KEY=
# Signature token for the Cal.com License API (used for self-hosted integrations)
# We will give you a token when we provide you with a license key this ensure you and only you can communicate with the Cal.com License API for your license key
CAL_SIGNATURE_TOKEN=
# The route to the Cal.com License API
CALCOM_PRIVATE_API_ROUTE=
CALCOM_PRIVATE_API_ROUTE="https://goblin.cal.com"
# ***********************************************************************************************************
# - DATABASE ************************************************************************************************
@@ -185,6 +184,7 @@ API_KEY_PREFIX=cal_
# allow access to the nodemailer transports from the .env file. E-mail templates are accessible within lib/emails/
# Configures the global From: header whilst sending emails.
EMAIL_FROM='notifications@yourselfhostedcal.com'
EMAIL_FROM_NAME='Cal.com'
# Configure SMTP settings (@see https://nodemailer.com/smtp/).
# Configuration to receive emails locally (mailhog)
-39
View File
@@ -1,39 +0,0 @@
---
name: Bug report
about: Report any issues with the platform
title: ""
labels: bug
assignees: ""
---
Found a bug? Please fill out the sections below. 👍
### Issue Summary
A summary of the issue. This needs to be a clear detailed-rich summary.
### Steps to Reproduce
1. (for example) Went to ...
2. Clicked on...
3. ...
Any other relevant information. For example, why do you consider this a bug and what did you expect to happen instead?
### Actual Results
- What's happening right now that is different from what is expected
### Expected Results
- This is an ideal result that the system should get after the tests are performed
### Technical details
- Browser version, screen recording, console logs, network requests: You can make a recording with [Bird Eats Bug](https://birdeatsbug.com/).
- Node.js version
- Anything else that you think could be an issue.
### Evidence
- How was this tested? This is quite mandatory in terms of bugs. Providing evidence of your testing with screenshots or/and videos is an amazing way to prove the bug and a troubleshooting chance to find the solution.
+81
View File
@@ -0,0 +1,81 @@
name: Bug report
description: "Please make sure you have checked for any duplicate issues."
title: "(bug)"
labels: ["🐛 bug"]
assignees: []
body:
- type: textarea
id: issue-summary
attributes:
label: Issue Summary
description: A summary of the issue. This needs to be a clear detailed-rich summary.
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to Reproduce
value: |
1. (for example) Went to ...
2. Clicked on...
3. ...
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen.
validations:
required: true
- type: textarea
id: other-information
attributes:
label: Other information
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
validations:
required: false
- type: checkboxes
id: environment
attributes:
label: Environment
options:
- label: app.cal.com
- label: Self-hosted Cal
- type: textarea
id: desktop-version
attributes:
label: Desktop (please complete the following information)
description: |
examples:
- **OS**: [e.g. iOS]
- **Browser**: [e.g. chrome, safari]
- **Version**: [e.g. 22]
value: |
- OS:
- Node:
- npm:
render: markdown
validations:
required: true
- type: markdown
id: nodejs-version
attributes:
value: |
#### Node.JS version
[e.g. v18.15.0]
- type: markdown
id: anything-else
attributes:
value: |
#### Anything else?
- Screen recording, console logs, network requests: You can make a recording with [Loom](https://www.loom.com).
- Anything else that you think could be an issue?
+2 -2
View File
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Questions
url: https://go.cal.com/discord
about: Ask a general question about the project on our discord community
url: https://github.com/calcom/cal.com/discussions
about: Need help selfhosting or ask a general question about the project? Open a discussion
+2 -1
View File
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest a feature or idea
title: ""
labels: feature
labels: ["✨ feature", "🚨 needs approval"]
assignees: ""
---
@@ -53,3 +53,4 @@ assignees: ""
- If this issue has a `🚨 needs approval` label, don't start coding yet. Wait until a core member approves feature request by removing this label, then you can start coding.
- For clarity: Non-core member issues automatically get the `🚨 needs approval` label.
- Your feature ideas are invaluable to us! However, they undergo review to ensure alignment with the product's direction.
- Follow Best Practices lined out in our [Contributor Docs](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
+3 -3
View File
@@ -11,9 +11,9 @@
## 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)
- [ ] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [ ] I have added a Docs issue [here](https://github.com/calcom/docs/issues/new) if this PR makes changes that would require a [documentation change](https://docs.cal.com). If N/A, write N/A here and check the checkbox.
- [ ] I confirm automated tests are in place that prove my fix is effective or that my feature works.
## How should this be tested?
+2 -1
View File
@@ -20,13 +20,14 @@ runs:
key-2: ${{ hashFiles('apps/**/**.[jt]s', 'apps/**/**.[jt]sx', 'packages/**/**.[jt]s', 'packages/**/**.[jt]sx', '!**/node_modules') }}
key-3: ${{ github.event.pull_request.number || github.ref }}
key-4: ${{ github.sha }}
key-5: ${{ github.event.pull_request.head.sha }}
with:
path: |
${{ github.workspace }}/apps/web/.next
${{ github.workspace }}/apps/web/public/embed
**/.turbo/**
**/dist/**
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}
key: ${{ runner.os }}-${{ env.cache-name }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}-${{ env.key-4 }}-${{ env.key-5 }}
- run: |
export NODE_OPTIONS="--max_old_space_size=8192"
yarn build
+2 -1
View File
@@ -17,6 +17,7 @@ runs:
cache-name: cache-db
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
key-2: ${{ github.event.pull_request.number || github.ref }}
key-3: ${{ github.event.pull_request.head.sha }}
DATABASE_URL: ${{ inputs.DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ inputs.DATABASE_URL }}
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
@@ -24,7 +25,7 @@ runs:
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
with:
path: ${{ inputs.path }}
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}
key: ${{ runner.os }}-${{ env.cache-name }}-${{ inputs.path }}-${{ env.key-1 }}-${{ env.key-2 }}-${{ env.key-3 }}
- run: echo ${{ env.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }} && yarn db-seed
if: steps.cache-db.outputs.cache-hit != 'true'
shell: bash
-6
View File
@@ -69,12 +69,6 @@ jobs:
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
analyze:
name: Analyze Build
needs: [build]
uses: ./.github/workflows/nextjs-bundle-analysis.yml
secrets: inherit
required:
needs: [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()
@@ -3,6 +3,9 @@ name: Production Builds
on:
workflow_call:
permissions:
contents: read
env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
@@ -65,7 +68,7 @@ jobs:
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/cache-db
- name: Cache API v1 production build
uses: buildjet/cache@v3
uses: buildjet/cache@v4
id: cache-api-v1-build
env:
cache-name: api-v1-build
@@ -10,6 +10,8 @@ env:
jobs:
build:
name: Build API v2
permissions:
contents: read
runs-on: buildjet-4vcpu-ubuntu-2204
timeout-minutes: 30
services:
@@ -34,7 +36,7 @@ jobs:
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- name: Cache API v2 production build
uses: buildjet/cache@v3
uses: buildjet/cache@v4
id: cache-api-v2-build
env:
cache-name: api-v2-build
+2
View File
@@ -3,6 +3,8 @@ on:
workflow_call:
env:
NODE_OPTIONS: --max-old-space-size=4096
permissions:
contents: read
jobs:
check-types:
runs-on: buildjet-4vcpu-ubuntu-2204
+79
View File
@@ -0,0 +1,79 @@
name: E2E
on:
workflow_call:
env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
API_KEY_PREFIX: ${{ secrets.CI_API_KEY_PREFIX }}
API_PORT: ${{ vars.CI_API_V2_PORT }}
CALCOM_LICENSE_KEY: ${{ secrets.CI_CALCOM_LICENSE_KEY }}
DAILY_API_KEY: ${{ secrets.CI_DAILY_API_KEY }}
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_READ_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_WRITE_URL: ${{ secrets.CI_DATABASE_URL }}
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
IS_E2E: true
NEXTAUTH_SECRET: ${{ secrets.CI_NEXTAUTH_SECRET }}
NEXTAUTH_URL: ${{ secrets.CI_NEXTAUTH_URL }}
NODE_OPTIONS: --max-old-space-size=4096
REDIS_URL: "redis://localhost:6379"
STRIPE_PRIVATE_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_API_KEY: ${{ secrets.CI_STRIPE_PRIVATE_KEY }}
STRIPE_CLIENT_ID: ${{ secrets.CI_STRIPE_CLIENT_ID }}
STRIPE_WEBHOOK_SECRET: ${{ secrets.CI_STRIPE_WEBHOOK_SECRET }}
jobs:
e2e:
timeout-minutes: 20
name: E2E API v2
runs-on: buildjet-8vcpu-ubuntu-2204
services:
postgres:
image: postgres:13
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: calendso
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
redis:
image: redis:latest
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
strategy:
fail-fast: false
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/yarn-playwright-install
- uses: ./.github/actions/cache-db
- name: Run Tests
working-directory: apps/api/v2
run: |
yarn test:e2e
EXIT_CODE=$?
echo "yarn test:e2e command exit code: $EXIT_CODE"
exit $EXIT_CODE
- name: Upload Test Results
if: ${{ always() }}
uses: actions/upload-artifact@v4
with:
name: test-results-api-v2
path: test-results
+3
View File
@@ -1,6 +1,9 @@
name: E2E App Store Tests
on:
workflow_call:
permissions:
actions: write
contents: read
env:
NODE_OPTIONS: --max-old-space-size=4096
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
+3
View File
@@ -1,6 +1,9 @@
name: E2E Embed React tests and booking flow (for non-embed as well)
on:
workflow_call:
permissions:
actions: write
contents: read
env:
NODE_OPTIONS: --max-old-space-size=4096
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
+3
View File
@@ -1,6 +1,9 @@
name: E2E Embed Core tests and booking flow (for non-embed as well)
on:
workflow_call:
permissions:
actions: write
contents: read
env:
NODE_OPTIONS: --max-old-space-size=4096
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
+3
View File
@@ -1,6 +1,9 @@
name: E2E
on:
workflow_call:
permissions:
actions: write
contents: read
env:
NODE_OPTIONS: --max-old-space-size=4096
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
+2
View File
@@ -1,6 +1,8 @@
name: Integration
on:
workflow_call:
permissions:
contents: read
env:
NODE_OPTIONS: --max-old-space-size=4096
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
+3 -7
View File
@@ -1,6 +1,9 @@
name: Lint
on:
workflow_call:
permissions:
actions: write
contents: read
jobs:
lint:
runs-on: buildjet-4vcpu-ubuntu-2204
@@ -16,13 +19,6 @@ jobs:
- name: Merge lint reports
run: jq -s '[.[]]|flatten' lint-results/*.json &> lint-results/eslint_report.json
- name: Annotate Code Linting Results
uses: ataylorme/eslint-annotate-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
report-json: "lint-results/eslint_report.json"
only-pr-files: false
- name: Upload ESLint report
if: ${{ always() }}
uses: actions/upload-artifact@v4
@@ -0,0 +1,81 @@
name: "Next.js Bundle Analysis Annotation"
on:
workflow_call:
workflow_dispatch:
permissions:
actions: read
contents: read
pull-requests: write
jobs:
annotate:
if: always()
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/yarn-install
- uses: ./.github/actions/cache-build
- name: Download base branch bundle stats
uses: dawidd6/action-download-artifact@v2
if: success()
with:
workflow: nextjs-bundle-analysis.yml
branch: ${{ github.event.pull_request.base.ref }}
path: apps/web/.next/analyze/base
# And here's the second place - this runs after we have both the current and
# base branch bundle stats, and will compare them to determine what changed.
# There are two configurable arguments that come from package.json:
#
# - budget: optional, set a budget (bytes) against which size changes are measured
# it's set to 350kb here by default, as informed by the following piece:
# https://infrequently.org/2021/03/the-performance-inequality-gap/
#
# - red-status-percentage: sets the percent size increase where you get a red
# status indicator, defaults to 20%
#
# Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
# entry in your package.json file.
- name: Compare with base branch bundle
if: success()
run: |
cd apps/web
ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
- name: Get comment body
id: get-comment-body
if: success() && github.event.number
run: |
cd apps/web
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
- name: Find Comment
uses: peter-evans/find-comment@v2
if: success() && github.event.number
id: fc
with:
issue-number: ${{ github.event.number }}
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
- name: Create Comment
uses: peter-evans/create-or-update-comment@v3
if: success() && github.event.number && steps.fc.outputs.comment-id == 0
with:
issue-number: ${{ github.event.number }}
body: ${{ steps.get-comment-body.outputs.body }}
- name: Update Comment
uses: peter-evans/create-or-update-comment@v3
if: success() && github.event.number && steps.fc.outputs.comment-id != 0
with:
issue-number: ${{ github.event.number }}
body: ${{ steps.get-comment-body.outputs.body }}
comment-id: ${{ steps.fc.outputs.comment-id }}
edit-mode: replace
+4 -68
View File
@@ -7,6 +7,10 @@ on:
branches:
- main
permissions:
actions: write
contents: read
env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
@@ -45,13 +49,7 @@ env:
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
jobs:
build:
name: Production builds
if: ${{ github.event_name == 'push' }}
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
analyze:
needs: build
if: always()
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
@@ -69,65 +67,3 @@ jobs:
with:
name: bundle
path: apps/web/.next/analyze/__bundle_analysis.json
- name: Download base branch bundle stats
uses: dawidd6/action-download-artifact@v2
if: success()
with:
workflow: nextjs-bundle-analysis.yml
branch: ${{ github.event.pull_request.base.ref }}
path: apps/web/.next/analyze/base
# And here's the second place - this runs after we have both the current and
# base branch bundle stats, and will compare them to determine what changed.
# There are two configurable arguments that come from package.json:
#
# - budget: optional, set a budget (bytes) against which size changes are measured
# it's set to 350kb here by default, as informed by the following piece:
# https://infrequently.org/2021/03/the-performance-inequality-gap/
#
# - red-status-percentage: sets the percent size increase where you get a red
# status indicator, defaults to 20%
#
# Either of these arguments can be changed or removed by editing the `nextBundleAnalysis`
# entry in your package.json file.
- name: Compare with base branch bundle
if: success()
run: |
cd apps/web
ls -laR .next/analyze/base && npx -p nextjs-bundle-analysis compare
- name: Get comment body
id: get-comment-body
if: success() && github.event.number
run: |
cd apps/web
body=$(cat .next/analyze/__bundle_analysis_comment.txt)
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
- name: Find Comment
uses: peter-evans/find-comment@v2
if: success() && github.event.number
id: fc
with:
issue-number: ${{ github.event.number }}
body-includes: "<!-- __NEXTJS_BUNDLE_@calcom/web -->"
- name: Create Comment
uses: peter-evans/create-or-update-comment@v3
if: success() && github.event.number && steps.fc.outputs.comment-id == 0
with:
issue-number: ${{ github.event.number }}
body: ${{ steps.get-comment-body.outputs.body }}
- name: Update Comment
uses: peter-evans/create-or-update-comment@v3
if: success() && github.event.number && steps.fc.outputs.comment-id != 0
with:
issue-number: ${{ github.event.number }}
body: ${{ steps.get-comment-body.outputs.body }}
comment-id: ${{ steps.fc.outputs.comment-id }}
edit-mode: replace
+17
View File
@@ -0,0 +1,17 @@
name: PR Reviewed
on:
pull_request_review:
types: [submitted]
jobs:
label-pr:
runs-on: ubuntu-latest
steps:
- name: Label PR as ready for E2E
if: github.event.review.state == 'approved'
uses: actions-ecosystem/action-add-labels@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
labels: 'ready-for-e2e'
+131 -5
View File
@@ -2,10 +2,16 @@ name: PR Update
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled]
branches:
- main
- gh-actions-test-branch
workflow_dispatch:
permissions:
actions: write
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
@@ -18,6 +24,7 @@ jobs:
pull-requests: read
outputs:
has-files-requiring-all-checks: ${{ steps.filter.outputs.has-files-requiring-all-checks }}
commit-sha: ${{ steps.get_sha.outputs.commit-sha }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
@@ -27,36 +34,155 @@ jobs:
filters: |
has-files-requiring-all-checks:
- "!(**.md|.github/CODEOWNERS)"
- name: Get Latest Commit SHA
id: get_sha
run: |
echo "commit-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
check-label:
needs: [changes]
runs-on: buildjet-2vcpu-ubuntu-2204
name: Check for E2E label
permissions:
pull-requests: read
outputs:
run-e2e: ${{ steps.check-if-pr-has-label.outputs.run-e2e == 'true' && (github.event.action != 'labeled' || (github.event.action == 'labeled' && github.event.label.name == 'ready-for-e2e')) }}
steps:
- name: Check if PR exists with ready-for-e2e label for this SHA
id: check-if-pr-has-label
uses: actions/github-script@v7
with:
script: |
let labels = [];
if (context.payload.pull_request) {
labels = context.payload.pull_request.labels;
} else {
try {
const sha = '${{ needs.changes.outputs.commit-sha }}';
console.log('sha', sha);
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: sha
});
if (prs.length === 0) {
core.setOutput('run-e2e', false);
console.log(`No pull requests found for commit SHA ${sha}`);
return;
}
const pr = prs[0];
console.log(`PR number: ${pr.number}`);
console.log(`PR title: ${pr.title}`);
console.log(`PR state: ${pr.state}`);
console.log(`PR URL: ${pr.html_url}`);
labels = pr.labels;
}
catch (e) {
core.setOutput('run-e2e', false);
console.log(e);
}
}
const labelFound = labels.map(l => l.name).includes('ready-for-e2e');
console.log('Found the label?', labelFound);
core.setOutput('run-e2e', labelFound);
type-check:
name: Type check
needs: [changes]
needs: [changes, check-label]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/check-types.yml
secrets: inherit
lint:
name: Linters
needs: [changes]
needs: [changes, check-label]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/lint.yml
secrets: inherit
unit-test:
name: Tests
needs: [changes]
needs: [changes, check-label]
if: ${{ needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/unit-tests.yml
secrets: inherit
build-api-v1:
name: Production builds
needs: [changes, check-label]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/api-v1-production-build.yml
secrets: inherit
build-api-v2:
name: Production builds
needs: [changes, check-label]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && 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, check-label]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && 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]
needs: [changes, check-label, 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: Tests
needs: [changes, check-label, build, build-api-v1, build-api-v2]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e.yml
secrets: inherit
e2e-api-v2:
name: Tests
needs: [changes, check-label, build, build-api-v1, build-api-v2]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-api-v2.yml
secrets: inherit
e2e-app-store:
name: Tests
needs: [changes, check-label, build, build-api-v1, build-api-v2]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-app-store.yml
secrets: inherit
e2e-embed:
name: Tests
needs: [changes, check-label, build, build-api-v1, build-api-v2]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-embed.yml
secrets: inherit
e2e-embed-react:
name: Tests
needs: [changes, check-label, build, build-api-v1, build-api-v2]
if: ${{ needs.check-label.outputs.run-e2e == 'true' && needs.changes.outputs.has-files-requiring-all-checks == 'true' }}
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
analyze:
name: Analyze Build
needs: [build]
uses: ./.github/workflows/nextjs-bundle-analysis.yml
secrets: inherit
required:
needs: [changes, lint, type-check, unit-test, integration-test]
needs: [changes, lint, type-check, unit-test, integration-test, check-label, build, build-api-v1, build-api-v2, e2e, e2e-api-v2, e2e-embed, e2e-embed-react, e2e-app-store]
if: always()
runs-on: buildjet-2vcpu-ubuntu-2204
steps:
-77
View File
@@ -1,77 +0,0 @@
name: Pre-release checks
on:
workflow_dispatch:
jobs:
changes:
name: Detect changes
runs-on: buildjet-4vcpu-ubuntu-2204
permissions:
pull-requests: read
outputs:
app-store: ${{ steps.filter.outputs.app-store }}
embed: ${{ steps.filter.outputs.embed }}
embed-react: ${{ steps.filter.outputs.embed-react }}
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/dangerous-git-checkout
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
app-store:
- 'apps/web/**'
- 'packages/app-store/**'
- 'playwright.config.ts'
embed:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
embed-react:
- 'apps/web/**'
- 'packages/embeds/**'
- 'playwright.config.ts'
lint:
name: Linters
uses: ./.github/workflows/lint.yml
secrets: inherit
build:
name: Production builds
uses: ./.github/workflows/production-build-without-database.yml
secrets: inherit
e2e:
name: E2E tests
needs: [changes, lint, build]
uses: ./.github/workflows/e2e.yml
secrets: inherit
e2e-app-store:
name: E2E App Store tests
needs: [changes, lint, build]
uses: ./.github/workflows/e2e-app-store.yml
secrets: inherit
e2e-embed:
name: E2E embeds tests
needs: [changes, lint, build]
uses: ./.github/workflows/e2e-embed.yml
secrets: inherit
e2e-embed-react:
name: E2E React embeds tests
needs: [changes, lint, build]
uses: ./.github/workflows/e2e-embed-react.yml
secrets: inherit
required:
needs: [changes, lint, build, e2e, e2e-app-store, e2e-embed, e2e-embed-react]
if: always()
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')
run: exit 1
@@ -2,6 +2,9 @@ name: Production Builds
on:
workflow_call:
permissions:
contents: read
env:
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
+7 -4
View File
@@ -1,9 +1,8 @@
name: Unit
on:
workflow_call:
workflow_run:
workflows: [Crowdin Action]
types: [completed]
permissions:
contents: read
jobs:
test:
name: Unit
@@ -13,7 +12,11 @@ jobs:
- 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
- run: yarn test
# We could add different timezones here that we need to run our tests in
- run: TZ=America/Los_Angeles yarn test -- --timeZoneDependentTestsOnly
- name: Run API v2 tests
working-directory: apps/api/v2
run: |
export NODE_OPTIONS="--max_old_space_size=8192"
yarn test
+3
View File
@@ -3,6 +3,9 @@ name: Yarn install
on:
workflow_call:
permissions:
contents: read
jobs:
setup:
name: Yarn install & cache
+1 -1
View File
@@ -74,7 +74,7 @@ branch are tagged into a release monthly.
To develop locally:
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your
1. [Fork](https://github.com/calcom/cal.com/fork/) this repository to your
own GitHub account and then
[clone](https://help.github.com/articles/cloning-a-repository/) it to your local device.
2. Create a new branch:
+33 -5
View File
File diff suppressed because one or more lines are too long
-2
View File
@@ -4,8 +4,6 @@ export function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// reduce sample rate to 10% on production
tracesSampleRate: process.env.NODE_ENV !== "production" ? 1.0 : 0.1,
});
}
@@ -0,0 +1,90 @@
import type { Request, Response } from "express";
import type { NextApiResponse, NextApiRequest } from "next";
import { createMocks } from "node-mocks-http";
import { describe, it, expect, vi } from "vitest";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { rateLimitApiKey } from "~/lib/helpers/rateLimitApiKey";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
checkRateLimitAndThrowError: vi.fn(),
}));
describe("rateLimitApiKey middleware", () => {
it("should return 401 if no apiKey is provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "GET",
query: {},
});
await rateLimitApiKey(req, res, vi.fn() as any);
expect(res._getStatusCode()).toBe(401);
expect(res._getJSONData()).toEqual({ message: "No apiKey provided" });
});
it("should call checkRateLimitAndThrowError with correct parameters", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});
(checkRateLimitAndThrowError as any).mockResolvedValueOnce({
limit: 100,
remaining: 99,
reset: Date.now(),
});
// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);
expect(checkRateLimitAndThrowError).toHaveBeenCalledWith({
identifier: "test-key",
rateLimitingType: "api",
onRateLimiterResponse: expect.any(Function),
});
});
it("should set rate limit headers correctly", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});
const rateLimiterResponse = {
limit: 100,
remaining: 99,
reset: Date.now(),
};
(checkRateLimitAndThrowError as any).mockImplementationOnce(({ onRateLimiterResponse }) => {
onRateLimiterResponse(rateLimiterResponse);
});
// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);
expect(res.getHeader("X-RateLimit-Limit")).toBe(rateLimiterResponse.limit);
expect(res.getHeader("X-RateLimit-Remaining")).toBe(rateLimiterResponse.remaining);
expect(res.getHeader("X-RateLimit-Reset")).toBe(rateLimiterResponse.reset);
});
it("should return 429 if rate limit is exceeded", async () => {
const { req, res } = createMocks({
method: "GET",
query: { apiKey: "test-key" },
});
(checkRateLimitAndThrowError as any).mockRejectedValue(new Error("Rate limit exceeded"));
// @ts-expect-error weird typing between middleware and createMocks
await rateLimitApiKey(req, res, vi.fn() as any);
expect(res._getStatusCode()).toBe(429);
expect(res._getJSONData()).toEqual({ message: "Rate limit exceeded" });
});
});
+13 -9
View File
@@ -6,15 +6,19 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// TODO: Add a way to add trusted api keys
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
});
try {
await checkRateLimitAndThrowError({
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
});
} catch (error) {
res.status(429).json({ message: "Rate limit exceeded" });
}
await next();
};
+6 -3
View File
@@ -1,7 +1,7 @@
import type { NextMiddleware } from "next-api-middleware";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import { hashAPIKey } from "@calcom/features/ee/api-keys/lib/apiKeys";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
@@ -18,9 +18,12 @@ export const dateNotInPast = function (date: Date) {
// This verifies the apiKey and sets the user if it is valid.
export const verifyApiKey: NextMiddleware = async (req, res, next) => {
const hasValidLicense = await checkLicense(prisma);
if (!hasValidLicense && IS_PRODUCTION)
const licenseKeyService = await LicenseKeyService.create();
const hasValidLicense = await licenseKeyService.checkLicense();
if (!hasValidLicense && IS_PRODUCTION) {
return res.status(401).json({ error: "Invalid or missing CALCOM_LICENSE_KEY environment variable" });
}
// Check if the apiKey query param is provided.
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
// remove the prefix from the user provided api_key. If no env set default to "cal_"
+3
View File
@@ -19,6 +19,9 @@ export const isAdminGuard = async (req: NextApiRequest) => {
accepted: true,
team: {
isOrganization: true,
organizationSettings: {
isAdminAPIEnabled: true,
},
},
OR: [{ role: MembershipRole.OWNER }, { role: MembershipRole.ADMIN }],
},
+2
View File
@@ -21,6 +21,8 @@ export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(sch
export const schemaBookingGetParams = z.object({
dateFrom: iso8601.optional(),
dateTo: iso8601.optional(),
order: z.enum(["asc", "desc"]).default("asc"),
sortBy: z.enum(["createdAt", "updatedAt"]).optional(),
});
const schemaBookingEditParams = z
+25 -1
View File
@@ -51,6 +51,18 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
* type: string
* format: email
* example: [john.doe@example.com, jane.doe@example.com]
* - in: query
* name: order
* required: false
* schema:
* type: string
* enum: [asc, desc]
* - in: query
* name: sortBy
* required: false
* schema:
* type: string
* enum: [createdAt, updatedAt]
* operationId: listBookings
* tags:
* - bookings
@@ -177,7 +189,7 @@ export async function handler(req: NextApiRequest) {
isOrganizationOwnerOrAdmin,
pagination: { take, skip },
} = req;
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
const { dateFrom, dateTo, order, sortBy } = schemaBookingGetParams.parse(req.query);
const args: Prisma.BookingFindManyArgs = {};
if (req.query.take && req.query.page) {
@@ -251,6 +263,18 @@ export async function handler(req: NextApiRequest) {
};
}
if (sortBy === "updatedAt") {
args.orderBy = {
updatedAt: order,
};
}
if (sortBy === "createdAt") {
args.orderBy = {
createdAt: order,
};
}
const data = await prisma.booking.findMany(args);
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
}
+4
View File
@@ -47,6 +47,10 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
* type: string
* format: date-time
* description: 'End time of the Event'
* rescheduleUid:
* type: string
* format: UID
* description: 'Uid of the booking to reschedule'
* responses:
* type: object
* required:
+3 -3
View File
@@ -278,8 +278,8 @@ async function postHandler(req: NextApiRequest) {
let data: Prisma.EventTypeCreateArgs["data"] = {
...parsedBody,
userId,
users: { connect: { id: userId } },
userId: !!parsedBody.teamId ? null : userId,
users: !!parsedBody.teamId ? undefined : { connect: { id: userId } },
bookingLimits: bookingLimits === null ? Prisma.DbNull : bookingLimits,
durationLimits: durationLimits === null ? Prisma.DbNull : durationLimits,
};
@@ -291,7 +291,7 @@ async function postHandler(req: NextApiRequest) {
await checkUserMembership(req);
}
if (isSystemWideAdmin && parsedBody.userId) {
if (isSystemWideAdmin && parsedBody.userId && !parsedBody.teamId) {
data = { ...parsedBody, users: { connect: { id: parsedBody.userId } } };
}
-1
View File
@@ -43,7 +43,6 @@ async function postHandler(req: NextApiRequest, res: NextApiResponse) {
language: data.language,
teamId: data.teamId,
usernameOrEmail: data.usernameOrEmail,
isOrg: data.isOrg,
});
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
+13 -4
View File
@@ -69,6 +69,9 @@ import { schemaTeamCreateBodyParams, schemaTeamReadPublic } from "~/lib/validati
* ownerId:
* type: number
* description: ID of the team owner - only admins can set this.
* parentId:
* type: number
* description: ID of the parent organization.
* tags:
* - teams
* responses:
@@ -99,7 +102,7 @@ async function postHandler(req: NextApiRequest) {
if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
}
// Check if parentId is related to this user
// Check if parentId is related to this user and is an organization
if (data.parentId) {
const parentTeam = await prisma.team.findFirst({
where: { id: data.parentId, members: { some: { userId, role: { in: ["OWNER", "ADMIN"] } } } },
@@ -107,7 +110,13 @@ async function postHandler(req: NextApiRequest) {
if (!parentTeam)
throw new HttpError({
statusCode: 401,
message: "Unauthorized: Invalid parent id. You can only use parent id of your own teams.",
message: "Unauthorized: Invalid parent id. You can only use parent id if you are org owner or admin.",
});
if (parentTeam.parentId)
throw new HttpError({
statusCode: 400,
message: "parentId must be of an organization, not a team.",
});
}
@@ -119,7 +128,7 @@ async function postHandler(req: NextApiRequest) {
metadata: data.metadata === null ? {} : data.metadata || undefined,
};
if (!IS_TEAM_BILLING_ENABLED) {
if (!IS_TEAM_BILLING_ENABLED || data.parentId) {
const team = await prisma.team.create({
data: {
...cloneData,
@@ -135,7 +144,7 @@ async function postHandler(req: NextApiRequest) {
return {
team: schemaTeamReadPublic.parse(team),
owner: schemaMembershipPublic.parse(team.members[0]),
message: `Team created successfully. We also made user with ID=${ownerId} the owner of this team.`,
message: `Team created successfully. We also made user with ID=${effectiveUserId} the owner of this team.`,
};
}
@@ -0,0 +1,112 @@
import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, expect, test } from "vitest";
import handler from "../../../pages/api/attendees/_post";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
describe("POST /api/attendees", () => {
describe("Errors", () => {
test("Returns 403 if user is not admin and has no booking", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
bookingId: 1,
email: "test@example.com",
name: "Test User",
timeZone: "UTC",
},
});
prismaMock.booking.findFirst.mockResolvedValue(null);
req.userId = 123;
// req.isAdmin = false;
await handler(req, res);
expect(res.statusCode).toBe(403);
expect(JSON.parse(res._getData()).message).toBe("Forbidden");
});
test("Returns 200 if user is admin", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
bookingId: 1,
email: "test@example.com",
name: "Test User",
timeZone: "UTC",
},
});
const attendeeData = {
id: 1,
email: "test@example.com",
name: "Test User",
timeZone: "UTC",
bookingId: 1,
};
prismaMock.attendee.create.mockResolvedValue(attendeeData);
req.isSystemWideAdmin = true;
req.userId = 123;
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(JSON.parse(res._getData()).attendee).toEqual(attendeeData);
expect(JSON.parse(res._getData()).message).toBe("Attendee created successfully");
});
test("Returns 200 if user is not admin but has a booking", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
bookingId: 1,
email: "test@example.com",
name: "Test User",
timeZone: "UTC",
},
});
const userBooking = { id: 1 };
prismaMock.booking.findFirst.mockResolvedValue(userBooking);
const attendeeData = {
id: 1,
email: "test@example.com",
name: "Test User",
timeZone: "UTC",
bookingId: 1,
};
prismaMock.attendee.create.mockResolvedValue(attendeeData);
req.userId = 123;
await handler(req, res);
expect(res.statusCode).toBe(200);
expect(JSON.parse(res._getData()).attendee).toEqual(attendeeData);
expect(JSON.parse(res._getData()).message).toBe("Attendee created successfully");
});
test("Returns 400 if request body is invalid", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
// Missing required fields
},
});
req.userId = 123;
await handler(req, res);
expect(res.statusCode).toBe(400);
});
});
});
+37 -1
View File
@@ -11,6 +11,7 @@ import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { buildBooking, buildEventType, buildWebhook } from "@calcom/lib/test/builder";
import prisma from "@calcom/prisma";
import type { Booking } from "@calcom/prisma";
import handler from "../../../pages/api/bookings/_post";
@@ -157,6 +158,7 @@ describe.skipIf(true)("POST /api/bookings", () => {
describe("Success", () => {
describe("Regular event-type", () => {
let createdBooking: Booking;
test("Creates one single booking", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
@@ -181,9 +183,43 @@ describe.skipIf(true)("POST /api/bookings", () => {
await handler(req, res);
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
createdBooking = JSON.parse(res._getData());
expect(prismaMock.booking.create).toHaveBeenCalledTimes(1);
});
test("Reschedule created booking", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
name: "testReschedule",
start: dayjs().add(2, "day").format(),
end: dayjs().add(3, "day").format(),
eventTypeId: 2,
email: "test@example.com",
location: "Cal.com Video",
timeZone: "America/Montevideo",
language: "en",
customInputs: [],
metadata: {},
userId: 4,
rescheduleUid: createdBooking.uid,
},
prisma,
});
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildEventType());
prismaMock.booking.findMany.mockResolvedValue([]);
await handler(req, res);
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
const rescheduledBooking = JSON.parse(res._getData()) as Booking;
expect(prismaMock.booking.create).toHaveBeenCalledTimes(1);
expect(rescheduledBooking.fromReschedule).toEqual(createdBooking.uid);
const previousBooking = await prisma.booking.findUnique({
where: { uid: createdBooking.uid },
});
expect(previousBooking?.status).toBe("cancelled");
});
});
describe("Recurring event-type", () => {
@@ -0,0 +1,157 @@
import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, expect, test, vi, afterEach } from "vitest";
import { buildEventType } from "@calcom/lib/test/builder";
import handler from "../../../pages/api/event-types/_post";
import checkParentEventOwnership from "../../../pages/api/event-types/_utils/checkParentEventOwnership";
import checkTeamEventEditPermission from "../../../pages/api/event-types/_utils/checkTeamEventEditPermission";
import checkUserMembership from "../../../pages/api/event-types/_utils/checkUserMembership";
import ensureOnlyMembersAsHosts from "../../../pages/api/event-types/_utils/ensureOnlyMembersAsHosts";
import { canUserAccessTeamWithRole } from "../../../pages/api/teams/[teamId]/_auth-middleware";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
const adminUserId = 1;
const memberUserId = 10;
vi.mock("../../../pages/api/teams/[teamId]/_auth-middleware", () => ({
canUserAccessTeamWithRole: vi.fn(),
}));
vi.mock("../../../pages/api/event-types/_utils/checkUserMembership", () => ({
default: vi.fn(),
}));
vi.mock("../../../pages/api/event-types/_utils/checkParentEventOwnership", () => ({
default: vi.fn(),
}));
vi.mock("../../../pages/api/event-types/_utils/checkTeamEventEditPermission", () => ({
default: vi.fn(),
}));
vi.mock("../../../pages/api/event-types/_utils/ensureOnlyMembersAsHosts", () => ({
default: vi.fn(),
}));
afterEach(() => {
vi.resetAllMocks();
});
describe("POST /api/event-types", () => {
describe("Errors", () => {
test("should throw 401 if a non-admin user tries to create an event type with userId", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
title: "Tennis class",
slug: "tennis-class-{{$guid}}",
length: 60,
hidden: true,
userId: memberUserId,
},
});
await handler(req, res);
expect(res.statusCode).toBe(401);
expect(JSON.parse(res._getData()).message).toBe("ADMIN required for `userId`");
});
test("should throw 401 if not system-wide admin and user cannot access teamId with required roles", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
title: "Tennis class",
slug: "tennis-class-{{$guid}}",
length: 60,
hidden: true,
teamId: 9999,
},
});
req.userId = memberUserId;
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
await handler(req, res);
expect(res.statusCode).toBe(401);
expect(JSON.parse(res._getData()).message).toBe("ADMIN required for `teamId`");
});
test("should throw 400 if system-wide admin but neither userId nor teamId provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
title: "Tennis class",
slug: "tennis-class-{{$guid}}",
length: 60,
hidden: true,
},
});
req.isSystemWideAdmin = true;
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(JSON.parse(res._getData()).message).toBe("`userId` or `teamId` required");
});
});
describe("Success", () => {
test("should call checkParentEventOwnership and checkUserMembership if parentId is present", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
title: "Tennis class",
slug: "tennis-class-{{$guid}}",
length: 60,
hidden: true,
userId: memberUserId,
parentId: 9999,
teamId: 9999,
},
});
req.isSystemWideAdmin = true;
req.userId = adminUserId;
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => false);
await handler(req, res);
expect(checkParentEventOwnership).toHaveBeenCalled();
expect(checkUserMembership).toHaveBeenCalled();
});
test("should create event type successfully if all conditions are met", async () => {
const eventTypeTest = buildEventType();
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
title: "test title",
slug: "test-slug",
length: 60,
hidden: true,
userId: memberUserId,
parentId: 9999,
teamId: 9999,
},
});
req.isSystemWideAdmin = true;
req.userId = adminUserId;
vi.mocked(canUserAccessTeamWithRole).mockImplementationOnce(async () => true);
vi.mocked(checkParentEventOwnership).mockImplementationOnce(async () => undefined);
vi.mocked(checkUserMembership).mockImplementationOnce(async () => undefined);
vi.mocked(checkTeamEventEditPermission).mockImplementationOnce(async () => undefined);
vi.mocked(ensureOnlyMembersAsHosts).mockImplementationOnce(async () => undefined);
prismaMock.eventType.create.mockResolvedValue(eventTypeTest);
await handler(req, res);
const data = JSON.parse(res._getData());
expect(prismaMock.eventType.create).toHaveBeenCalledTimes(1);
expect(res.statusCode).toBe(200);
expect(data.message).toBe("Event type created successfully");
});
});
});
@@ -3,9 +3,9 @@ import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, vi, it, expect, afterEach } from "vitest";
import { describe, vi, it, expect, afterEach, beforeEach } from "vitest";
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
import LicenseKeyService from "@calcom/ee/common/server/LicenseKeyService";
import prisma from "@calcom/prisma";
import { isAdminGuard } from "~/lib/utils/isAdmin";
@@ -20,20 +20,21 @@ afterEach(() => {
vi.resetAllMocks();
});
vi.mock("@calcom/features/ee/common/server/checkLicense", () => {
return {
default: vi.fn(),
};
});
vi.mock("~/lib/utils/isAdmin", () => {
return {
isAdminGuard: vi.fn(),
};
});
vi.mock("@calcom/prisma");
vi.mock("~/lib/utils/isAdmin", () => ({
isAdminGuard: vi.fn(),
}));
describe("Verify API key", () => {
it("It should throw an error if the api key is not valid", async () => {
let service: LicenseKeyService;
beforeEach(async () => {
service = await LicenseKeyService.create();
vi.spyOn(service, "checkLicense");
});
it("should throw an error if the api key is not valid", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -43,7 +44,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(false);
vi.mocked(service.checkLicense).mockResolvedValue(false);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -53,9 +54,11 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
it("It should throw an error if no api key is provided", async () => {
it("should throw an error if no api key is provided", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -65,7 +68,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: false, scope: null });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -75,10 +78,11 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(res.statusCode).toBe(401);
});
it("It should set correct permissions for system-wide admin", async () => {
it("should set correct permissions for system-wide admin", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -97,7 +101,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.SystemWide });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -107,11 +111,12 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(req.isSystemWideAdmin).toBe(true);
expect(req.isOrganizationOwnerOrAdmin).toBe(false);
});
it("It should set correct permissions for org-level admin", async () => {
it("should set correct permissions for org-level admin", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -130,7 +135,7 @@ describe("Verify API key", () => {
fn: verifyApiKey,
};
vi.mocked(checkLicense).mockResolvedValue(true);
vi.mocked(service.checkLicense).mockResolvedValue(true);
vi.mocked(isAdminGuard).mockResolvedValue({ isAdmin: true, scope: ScopeOfAdmin.OrgOwnerOrAdmin });
const serverNext = vi.fn((next: void) => Promise.resolve(next));
@@ -140,6 +145,7 @@ describe("Verify API key", () => {
await middleware.fn(req, res, serverNext);
expect(middlewareSpy).toBeCalled();
expect(req.isSystemWideAdmin).toBe(false);
expect(req.isOrganizationOwnerOrAdmin).toBe(true);
});
@@ -0,0 +1,121 @@
import prismaMock from "../../../../../../tests/libs/__mocks__/prismaMock";
import type { Request, Response } from "express";
import type { NextApiRequest, NextApiResponse } from "next";
import { createMocks } from "node-mocks-http";
import { describe, expect, test } from "vitest";
import { HttpError } from "@calcom/lib/http-error";
import handler from "../../../pages/api/selected-calendars/_post";
type CustomNextApiRequest = NextApiRequest & Request;
type CustomNextApiResponse = NextApiResponse & Response;
describe("POST /api/selected-calendars", () => {
describe("Errors", () => {
test("Returns 403 if non-admin user tries to set userId in body", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
integration: "google",
externalId: "ext123",
userId: 444444,
},
});
req.userId = 333333;
try {
await handler(req, res);
} catch (e) {
expect(e).toBeInstanceOf(HttpError);
expect((e as HttpError).statusCode).toBe(403);
expect((e as HttpError).message).toBe("ADMIN required for userId");
}
});
test("Returns 400 if request body is invalid", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {
integration: "google",
},
});
req.userId = 333333;
req.isSystemWideAdmin = true;
await handler(req, res);
expect(res.statusCode).toBe(400);
expect(JSON.parse(res._getData()).message).toBe("invalid_type in 'externalId': Required");
});
});
describe("Success", () => {
test("Creates selected calendar if user is admin and sets bodyUserId", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
query: {
apiKey: "validApiKey",
},
body: {
integration: "google",
externalId: "ext123",
userId: 444444,
},
});
req.userId = 333333;
req.isSystemWideAdmin = true;
prismaMock.user.findFirstOrThrow.mockResolvedValue({
id: 444444,
} as any);
prismaMock.selectedCalendar.create.mockResolvedValue({
credentialId: 1,
integration: "google",
externalId: "ext123",
userId: 444444,
});
await handler(req, res);
expect(res.statusCode).toBe(200);
const responseData = JSON.parse(res._getData());
expect(responseData.selected_calendar.credentialId).toBe(1);
expect(responseData.message).toBe("Selected Calendar created successfully");
});
test("Creates selected calendar if user is non-admin and does not set bodyUserId", async () => {
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
query: {
apiKey: "validApiKey",
},
body: {
integration: "google",
externalId: "ext123",
},
});
req.userId = 333333;
prismaMock.selectedCalendar.create.mockResolvedValue({
credentialId: 1,
integration: "google",
externalId: "ext123",
userId: 333333,
});
await handler(req, res);
expect(res.statusCode).toBe(200);
const responseData = JSON.parse(res._getData());
expect(responseData.selected_calendar.credentialId).toBe(1);
expect(responseData.message).toBe("Selected Calendar created successfully");
});
});
});
@@ -58,7 +58,7 @@ describe("isAdmin guard", () => {
expect(scope).toBe(ScopeOfAdmin.SystemWide);
});
it("Returns org-wide admin when user is set as such", async () => {
it("Returns org-wide admin when user is set as such & admin API access is granted", async () => {
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
@@ -67,10 +67,21 @@ describe("isAdmin guard", () => {
const adminUser = await prisma.user.findFirstOrThrow({ where: { email: "owner1-acme@example.com" } });
req.userId = adminUser.id;
const { isAdmin, scope } = await isAdminGuard(req);
expect(isAdmin).toBe(true);
expect(scope).toBe(ScopeOfAdmin.OrgOwnerOrAdmin);
});
it("Returns no admin when user is set as org admin but admin API access is revoked", async () => {
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
method: "POST",
body: {},
});
const adminUser = await prisma.user.findFirstOrThrow({ where: { email: "owner1-dunder@example.com" } });
req.userId = adminUser.id;
const { isAdmin } = await isAdminGuard(req);
expect(isAdmin).toBe(false);
});
});
+7
View File
@@ -14,9 +14,16 @@ NEXT_PUBLIC_SENTRY_DSN=
# Stripe Billing
STRIPE_PRICE_ID_STARTER=
STRIPE_PRICE_ID_STARTER_OVERAGE=
STRIPE_PRICE_ID_ESSENTIALS=
STRIPE_PRICE_ID_ESSENTIALS_OVERAGE=
STRIPE_PRICE_ID_ENTERPRISE=
STRIPE_PRICE_ID_ENTERPRISE_OVERAGE=
STRIPE_API_KEY=
STRIPE_WEBHOOK_SECRET=
WEB_APP_URL=http://localhost:3000/
CALCOM_LICENSE_KEY=
API_KEY_PREFIX=cal_
GET_LICENSE_KEY_URL="https://console.cal.com/api/license"
IS_E2E=false
+12 -2
View File
@@ -1,5 +1,5 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
<a href="http://nestjs.com/" target="blank"><img src="https://github.com/user-attachments/assets/ce7c2ecf-6097-469a-8512-c846a9fb665d" height="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
@@ -24,7 +24,7 @@
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
Cal.com is using the [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
@@ -42,6 +42,16 @@ $ yarn prisma generate
Copy `.env.example` to `.env` and fill values.
## Add license Key to deployments table in DB
id, logo theme licenseKey agreedLicenseAt
1, null, null, 'c4234812-12ab-42s6-a1e3-55bedd4a5bb7', '2023-05-15 21:39:47.611'
your CALCOM_LICENSE_KEY env var need to contain the same value
.env
CALCOM_LICENSE_KEY=c4234812-12ab-42s6-a1e3-55bedd4a5bb
## Running the app
```bash
+2 -1
View File
@@ -10,5 +10,6 @@
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"setupFiles": ["<rootDir>/test/setEnvVars.ts"]
"setupFiles": ["<rootDir>/test/setEnvVars.ts"],
"reporters": ["default", "jest-summarizing-reporter"]
}
+4 -2
View File
@@ -18,7 +18,7 @@
"test:watch": "yarn dev:build && jest --watch",
"test:cov": "yarn dev:build && jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "yarn dev:build && jest --runInBand --config ./jest-e2e.json",
"test:e2e": "yarn dev:build && jest --runInBand --detectOpenHandles --forceExit --config ./jest-e2e.json",
"prisma": "yarn workspace @calcom/prisma prisma",
"generate-schemas": "yarn prisma generate && yarn prisma format",
"copy-swagger-module": "ts-node -r tsconfig-paths/register swagger/copy-swagger-module.ts"
@@ -26,12 +26,13 @@
"dependencies": {
"@calcom/platform-constants": "*",
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
"@calcom/platform-libraries-0.0.4": "npm:@calcom/platform-libraries@0.0.4",
"@calcom/platform-libraries-0.0.23": "npm:@calcom/platform-libraries@0.0.23",
"@calcom/platform-types": "*",
"@calcom/platform-utils": "*",
"@calcom/prisma": "*",
"@golevelup/ts-jest": "^0.4.0",
"@microsoft/microsoft-graph-types-beta": "^0.42.0-preview",
"@nestjs/bull": "^10.1.1",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.0.0",
@@ -42,6 +43,7 @@
"@nestjs/throttler": "^5.1.2",
"@sentry/node": "^8.8.0",
"body-parser": "^1.20.2",
"bull": "^4.12.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
+14 -10
View File
@@ -11,10 +11,11 @@ import { JwtModule } from "@/modules/jwt/jwt.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { RedisModule } from "@/modules/redis/redis.module";
import { RedisService } from "@/modules/redis/redis.service";
import { BullModule } from "@nestjs/bull";
import { MiddlewareConsumer, Module, NestModule, RequestMethod } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { APP_INTERCEPTOR, RouterModule } from "@nestjs/core";
import { seconds, ThrottlerModule } from "@nestjs/throttler";
import { APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
import { seconds, ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
import { AppController } from "./app.controller";
@@ -26,21 +27,20 @@ import { AppController } from "./app.controller";
isGlobal: true,
load: [appConfig],
}),
RedisModule,
BullModule.forRoot({
redis: `${process.env.REDIS_URL}${process.env.NODE_ENV === "production" ? "?tls=true" : ""}`,
}),
ThrottlerModule.forRootAsync({
imports: [RedisModule],
inject: [RedisService],
useFactory: (redisService: RedisService) => ({
throttlers: [
{
name: "short",
ttl: seconds(10),
limit: 3,
},
{
name: "medium",
ttl: seconds(30),
limit: 10,
name: "long",
ttl: seconds(60), // Time to live for the long period in seconds
limit: 120, // Maximum number of requests within the long ttl
},
],
storage: new ThrottlerStorageRedisService(redisService.redis),
@@ -57,6 +57,10 @@ import { AppController } from "./app.controller";
provide: APP_INTERCEPTOR,
useClass: ResponseInterceptor,
},
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule implements NestModule {
-2
View File
@@ -30,9 +30,7 @@ export const bootstrap = (app: NestExpressApplication): NestExpressApplication =
type: VersioningType.CUSTOM,
extractor: (request: unknown) => {
const headerVersion = (request as Request)?.headers[CAL_API_VERSION_HEADER] as string | undefined;
console.log("asap header headerVersion", headerVersion);
if (headerVersion && API_VERSIONS.includes(headerVersion as API_VERSIONS_ENUM)) {
console.log("asap return header headerVersion", headerVersion);
return headerVersion;
}
return VERSION_2024_04_15;
+4
View File
@@ -15,6 +15,9 @@ const loadConfig = (): AppConfig => {
? `:${Number(getEnv("API_PORT", "5555"))}`
: ""
}/v2`,
keyPrefix: getEnv("API_KEY_PREFIX", "cal_"),
licenseKey: getEnv("CALCOM_LICENSE_KEY", ""),
licenseKeyUrl: getEnv("GET_LICENSE_KEY_URL", "https://console.cal.com/api/license"),
},
db: {
readUrl: getEnv("DATABASE_READ_URL"),
@@ -31,6 +34,7 @@ const loadConfig = (): AppConfig => {
app: {
baseUrl: getEnv("WEB_APP_URL", "https://app.cal.com"),
},
e2e: getEnv("IS_E2E", false),
};
};
+4
View File
@@ -6,6 +6,9 @@ export type AppConfig = {
port: number;
path: string;
url: string;
keyPrefix: string;
licenseKey: string;
licenseKeyUrl: string;
};
db: {
readUrl: string;
@@ -22,4 +25,5 @@ export type AppConfig = {
app: {
baseUrl: string;
};
e2e: boolean;
};
@@ -1,5 +1,6 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
@@ -16,10 +17,10 @@ import * as request from "supertest";
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { handleNewBooking } from "@calcom/platform-libraries-0.0.2";
import { handleNewBooking } from "@calcom/platform-libraries-0.0.23";
import { ApiSuccessResponse, ApiResponse } from "@calcom/platform-types";
describe("Bookings Endpoints", () => {
@@ -39,7 +40,7 @@ describe("Bookings Endpoints", () => {
let createdBooking: Awaited<ReturnType<typeof handleNewBooking>>;
beforeAll(async () => {
const moduleRef = await withAccessTokenAuth(
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
imports: [AppModule, PrismaModule, UsersModule, SchedulesModule_2024_04_15],
@@ -90,7 +91,10 @@ describe("Bookings Endpoints", () => {
const bookingTimeZone = "Europe/London";
const bookingLanguage = "en";
const bookingHashedLink = "";
const bookingMetadata = {};
const bookingMetadata = {
timeFormat: "12",
meetingType: "organizer-phone",
};
const bookingResponses = {
name: "tester",
email: "tester@example.com",
@@ -102,7 +106,7 @@ describe("Bookings Endpoints", () => {
guests: [],
};
const body = {
const body: CreateBookingInput = {
start: bookingStart,
end: bookingEnd,
eventTypeId: bookingEventTypeId,
@@ -135,11 +139,68 @@ describe("Bookings Endpoints", () => {
});
});
describe("should reschedule a booking", () => {
it("should reschedule with updated start time, end time & metadata", async () => {
const newBookingStart = "2040-05-21T12:30:00.000Z";
const newBookingEnd = "2040-05-21T13:30:00.000Z";
const bookingEventTypeId = eventTypeId;
const bookingTimeZone = "Europe/London";
const bookingLanguage = "en";
const bookingHashedLink = "";
const newBookingMetadata = {
timeFormat: "24",
meetingType: "attendee-phone",
};
const bookingResponses = {
name: "tester",
email: "tester@example.com",
location: {
value: "link",
optionValue: "",
},
notes: "test",
guests: [],
};
const body: CreateBookingInput = {
rescheduleUid: createdBooking.uid,
start: newBookingStart,
end: newBookingEnd,
eventTypeId: bookingEventTypeId,
timeZone: bookingTimeZone,
language: bookingLanguage,
metadata: newBookingMetadata,
hashedLink: bookingHashedLink,
responses: bookingResponses,
};
return request(app.getHttpServer())
.post("/v2/bookings")
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<Awaited<ReturnType<typeof handleNewBooking>>> =
response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.userPrimaryEmail).toBeDefined();
expect(responseBody.data.userPrimaryEmail).toEqual(userEmail);
expect(responseBody.data.id).toBeDefined();
expect(responseBody.data.uid).toBeDefined();
expect(responseBody.data.startTime).toEqual(newBookingStart);
expect(responseBody.data.eventTypeId).toEqual(bookingEventTypeId);
expect(responseBody.data.user.timeZone).toEqual(bookingTimeZone);
expect(responseBody.data.metadata).toEqual(newBookingMetadata);
createdBooking = responseBody.data;
});
});
});
it("should get bookings", async () => {
return request(app.getHttpServer())
.get("/v2/bookings?filters[status]=upcoming")
.then((response) => {
console.log("asap responseBody", JSON.stringify(response.body, null, 2));
const responseBody: GetBookingsOutput = response.body;
const fetchedBooking = responseBody.data.bookings[0];
@@ -240,7 +301,8 @@ describe("Bookings Endpoints", () => {
// });
// });
it("should cancel a booking", async () => {
// cancelling a booking hangs the test for some reason
it.skip("should cancel a booking", async () => {
const bookingId = createdBooking.id;
const body = {
@@ -1,11 +1,13 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { CreateRecurringBookingInput } from "@/ee/bookings/inputs/create-recurring-booking.input";
import { MarkNoShowInput } from "@/ee/bookings/inputs/mark-no-show.input";
import { GetBookingOutput } from "@/ee/bookings/outputs/get-booking.output";
import { GetBookingsOutput } from "@/ee/bookings/outputs/get-bookings.output";
import { MarkNoShowOutput } from "@/ee/bookings/outputs/mark-no-show.output";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { BillingService } from "@/modules/billing/services/billing.service";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
@@ -32,20 +34,19 @@ import { Request } from "express";
import { NextApiRequest } from "next/types";
import { X_CAL_CLIENT_ID } from "@calcom/platform-constants";
import { BOOKING_READ, SUCCESS_STATUS } from "@calcom/platform-constants";
import {
getAllUserBookings,
getBookingInfo,
handleCancelBooking,
getBookingForReschedule,
} from "@calcom/platform-libraries-0.0.2";
import { BOOKING_READ, SUCCESS_STATUS, BOOKING_WRITE } from "@calcom/platform-constants";
import {
handleNewBooking,
BookingResponse,
HttpError,
handleNewRecurringBooking,
handleInstantMeeting,
} from "@calcom/platform-libraries-0.0.2";
handleMarkNoShow,
getAllUserBookings,
getBookingInfo,
handleCancelBooking,
getBookingForReschedule,
} from "@calcom/platform-libraries-0.0.23";
import { GetBookingsInput, CancelBookingInput, Status } from "@calcom/platform-types";
import { ApiResponse } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@@ -89,7 +90,7 @@ export class BookingsController {
) {}
@Get("/")
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
@Permissions([BOOKING_READ])
@ApiQuery({ name: "filters[status]", enum: Status, required: true })
@ApiQuery({ name: "limit", type: "number", required: false })
@@ -149,17 +150,21 @@ export class BookingsController {
@Req() req: BookingRequest,
@Body() body: CreateBookingInput,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse<unknown>> {
): Promise<ApiResponse<Partial<BookingResponse>>> {
const oAuthClientId = clientId?.toString();
const { orgSlug, locationUrl } = body;
req.headers["x-cal-force-slug"] = orgSlug;
try {
const booking = await handleNewBooking(
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl)
);
void (await this.billingService.increaseUsageByClientId(oAuthClientId!));
if (booking.userId && booking.uid && booking.startTime) {
void (await this.billingService.increaseUsageByUserId(booking.userId, {
uid: booking.uid,
startTime: booking.startTime,
fromReschedule: booking.fromReschedule,
}));
}
return {
status: SUCCESS_STATUS,
data: booking,
@@ -176,14 +181,22 @@ export class BookingsController {
@Param("bookingId") bookingId: string,
@Body() _: CancelBookingInput,
@Headers(X_CAL_CLIENT_ID) clientId?: string
): Promise<ApiResponse> {
): Promise<ApiResponse<{ bookingId: number; bookingUid: string; onlyRemovedAttendee: boolean }>> {
const oAuthClientId = clientId?.toString();
if (bookingId) {
try {
req.body.id = parseInt(bookingId);
await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
const res = await handleCancelBooking(await this.createNextApiBookingRequest(req, oAuthClientId));
if (!res.onlyRemovedAttendee) {
void (await this.billingService.cancelUsageByBookingUid(res.bookingUid));
}
return {
status: SUCCESS_STATUS,
data: {
bookingId: res.bookingId,
bookingUid: res.bookingUid,
onlyRemovedAttendee: res.onlyRemovedAttendee,
},
};
} catch (err) {
this.handleBookingErrors(err);
@@ -194,6 +207,27 @@ export class BookingsController {
throw new InternalServerErrorException("Could not cancel booking.");
}
@Post("/:bookingUid/mark-no-show")
@Permissions([BOOKING_WRITE])
@UseGuards(ApiAuthGuard)
async markNoShow(
@Body() body: MarkNoShowInput,
@Param("bookingUid") bookingUid: string
): Promise<MarkNoShowOutput> {
try {
const markNoShowResponse = await handleMarkNoShow({
bookingUid: bookingUid,
attendees: body.attendees,
noShowHost: body.noShowHost,
});
return { status: SUCCESS_STATUS, data: markNoShowResponse };
} catch (err) {
this.handleBookingErrors(err, "no-show");
}
throw new InternalServerErrorException("Could not mark no show.");
}
@Post("/recurring")
async createRecurringBooking(
@Req() req: BookingRequest,
@@ -206,7 +240,14 @@ export class BookingsController {
await this.createNextApiBookingRequest(req, oAuthClientId)
);
void (await this.billingService.increaseUsageByClientId(oAuthClientId!));
createdBookings.forEach(async (booking) => {
if (booking.userId && booking.uid && booking.startTime) {
void (await this.billingService.increaseUsageByUserId(booking.userId, {
uid: booking.uid,
startTime: booking.startTime,
}));
}
});
return {
status: SUCCESS_STATUS,
@@ -231,7 +272,15 @@ export class BookingsController {
await this.createNextApiBookingRequest(req, oAuthClientId)
);
void (await this.billingService.increaseUsageByClientId(oAuthClientId!));
if (instantMeeting.userId && instantMeeting.bookingUid) {
const now = new Date();
// add a 10 secondes delay to the usage incrementation to give some time to cancel the booking if needed
now.setSeconds(now.getSeconds() + 10);
void (await this.billingService.increaseUsageByUserId(instantMeeting.userId, {
uid: instantMeeting.bookingUid,
startTime: now,
}));
}
return {
status: SUCCESS_STATUS,
@@ -287,8 +336,14 @@ export class BookingsController {
return req as unknown as NextApiRequest & { userId?: number } & OAuthRequestParams;
}
private handleBookingErrors(err: Error | HttpError | unknown, type?: "recurring" | `instant`): void {
const errMsg = `Error while creating ${type ? type + " " : ""}booking.`;
private handleBookingErrors(
err: Error | HttpError | unknown,
type?: "recurring" | `instant` | "no-show"
): void {
const errMsg =
type === "no-show"
? `Error while marking no-show.`
: `Error while creating ${type ? type + " " : ""}booking.`;
if (err instanceof HttpError) {
const httpError = err as HttpError;
throw new HttpException(httpError?.message ?? errMsg, httpError?.statusCode ?? 500);
@@ -1,7 +1,7 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { IsBoolean, IsNumber, IsOptional } from "class-validator";
import type { AppsStatus } from "@calcom/platform-libraries-0.0.2";
import type { AppsStatus } from "@calcom/platform-libraries-0.0.23";
export class CreateRecurringBookingInput extends CreateBookingInput {
@IsBoolean()
@@ -0,0 +1,22 @@
import { Type } from "class-transformer";
import { IsOptional, IsArray, IsEmail, IsBoolean, ValidateNested } from "class-validator";
class Attendee {
@IsEmail()
email!: string;
@IsBoolean()
noShow!: boolean;
}
export class MarkNoShowInput {
@IsBoolean()
@IsOptional()
noShowHost?: boolean;
@ValidateNested()
@Type(() => Attendee)
@IsArray()
@IsOptional()
attendees?: Attendee[];
}
@@ -0,0 +1,45 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsString, IsEnum, IsOptional, ValidateNested, IsArray, IsEmail, IsBoolean } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
class Attendee {
@IsEmail()
email!: string;
@IsBoolean()
noShow!: boolean;
}
class HandleMarkNoShowData {
@IsString()
message!: string;
@IsBoolean()
@IsOptional()
noShowHost?: boolean;
@IsString()
@IsOptional()
messageKey?: string;
@ValidateNested()
@Type(() => Attendee)
@IsArray()
@IsOptional()
attendees?: Attendee[];
}
export class MarkNoShowOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: HandleMarkNoShowData,
})
@ValidateNested()
@Type(() => HandleMarkNoShowData)
data!: HandleMarkNoShowData;
}
@@ -7,6 +7,11 @@ export interface CalendarApp {
check(userId: number): Promise<ApiResponse>;
}
export interface CredentialSyncCalendarApp {
save(userId: number, userEmail: string, username: string, password: string): Promise<{ status: string }>;
check(userId: number): Promise<ApiResponse>;
}
export interface OAuthCalendarApp extends CalendarApp {
connect(authorization: string, req: Request): Promise<ApiResponse<{ authUrl: string }>>;
}
@@ -1,4 +1,6 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { CalendarsController } from "@/ee/calendars/controllers/calendars.controller";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
import { OutlookService } from "@/ee/calendars/services/outlook.service";
@@ -17,8 +19,10 @@ import { Module } from "@nestjs/common";
CalendarsService,
OutlookService,
GoogleCalendarService,
AppleCalendarService,
SelectedCalendarsRepository,
AppsRepository,
CalendarsRepository,
],
controllers: [CalendarsController],
exports: [CalendarsService],
@@ -0,0 +1,51 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
const credentialForCalendarRepositorySelect = Prisma.validator<Prisma.CredentialSelect>()({
id: true,
appId: true,
type: true,
userId: true,
user: {
select: {
email: true,
},
},
teamId: true,
key: true,
invalid: true,
});
@Injectable()
export class CalendarsRepository {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async getCalendarCredentials(credentialId: number, userId: number) {
return await this.dbRead.prisma.credential.findFirst({
where: {
id: credentialId,
userId,
},
select: {
...credentialForCalendarRepositorySelect,
app: {
select: {
slug: true,
categories: true,
dirName: true,
},
},
},
});
}
async deleteCredentials(credentialId: number) {
return await this.dbWrite.prisma.credential.delete({
where: {
id: credentialId,
},
});
}
}
@@ -1,5 +1,6 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { DeletedCalendarCredentialsOutputResponseDto } from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
@@ -16,7 +17,9 @@ import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-cli
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import {
GOOGLE_CALENDAR,
OFFICE_365_CALENDAR,
@@ -27,20 +30,6 @@ import { OFFICE_365_CALENDAR_ID, OFFICE_365_CALENDAR_TYPE } from "@calcom/platfo
const CLIENT_REDIRECT_URI = "http://localhost:5555";
class CalendarsServiceMock {
async getCalendars() {
return {
connectedCalendars: [
{
integration: {
type: "google_calendar",
},
},
],
};
}
}
describe("Platform Calendars Endpoints", () => {
let app: INestApplication;
@@ -66,8 +55,7 @@ describe("Platform Calendars Endpoints", () => {
.useValue({
canActivate: () => true,
})
.overrideProvider(CalendarsService)
.useClass(CalendarsServiceMock)
.compile();
app = moduleRef.createNestApplication();
@@ -85,6 +73,9 @@ describe("Platform Calendars Endpoints", () => {
accessTokenSecret = tokens.accessToken;
refreshTokenSecret = tokens.refreshToken;
await app.init();
jest
.spyOn(CalendarsService.prototype, "getCalendars")
.mockImplementation(CalendarsServiceMock.prototype.getCalendars);
});
async function createOAuthClient(organizationId: number) {
@@ -116,15 +107,16 @@ describe("Platform Calendars Endpoints", () => {
.expect(401);
});
it(`/GET/v2/calendars/${OFFICE_365_CALENDAR}/connect: it should redirect to auth-url for office 365 calendar oauth with valid access token `, async () => {
const response = await request(app.getHttpServer())
.get(`/v2/calendars/${OFFICE_365_CALENDAR}/connect`)
.set("Authorization", `Bearer ${accessTokenSecret}`)
.set("Origin", CLIENT_REDIRECT_URI)
.expect(200);
const data = response.body.data;
expect(data.authUrl).toBeDefined();
});
// TODO: Uncomment this once CI is ready to run proper Office365 tests
// it(`/GET/v2/calendars/${OFFICE_365_CALENDAR}/connect: it should redirect to auth-url for office 365 calendar oauth with valid access token `, async () => {
// const response = await request(app.getHttpServer())
// .get(`/v2/calendars/${OFFICE_365_CALENDAR}/connect`)
// .set("Authorization", `Bearer ${accessTokenSecret}`)
// .set("Origin", CLIENT_REDIRECT_URI)
// .expect(200);
// const data = response.body.data;
// expect(data.authUrl).toBeDefined();
// });
it(`/GET/v2/calendars/${GOOGLE_CALENDAR}/connect: it should redirect to auth-url for google calendar oauth with valid access token `, async () => {
const response = await request(app.getHttpServer())
@@ -200,6 +192,26 @@ describe("Platform Calendars Endpoints", () => {
.expect(200);
});
it.skip(`/POST/v2/calendars/${OFFICE_365_CALENDAR}/disconnect: it should respond with a 201 returning back the user deleted calendar credentials`, async () => {
const body = {
id: 10,
};
return request(app.getHttpServer())
.post(`/v2/calendars/${OFFICE_365_CALENDAR}/disconnect`)
.set("Authorization", `Bearer ${accessTokenSecret}`)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: Promise<DeletedCalendarCredentialsOutputResponseDto> = response.body;
expect((await responseBody).status).toEqual(SUCCESS_STATUS);
expect((await responseBody).data).toBeDefined();
expect((await responseBody).data.id).toEqual(body.id);
expect((await responseBody).data.userId).toEqual(user.id);
});
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
@@ -1,12 +1,19 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { DeleteCalendarCredentialsInputBodyDto } from "@/ee/calendars/input/delete-calendar-credentials.input";
import { GetBusyTimesOutput } from "@/ee/calendars/outputs/busy-times.output";
import { ConnectedCalendarsOutput } from "@/ee/calendars/outputs/connected-calendars.output";
import {
DeletedCalendarCredentialsOutputResponseDto,
DeletedCalendarCredentialsOutputDto,
} from "@/ee/calendars/outputs/delete-calendar-credentials.output";
import { AppleCalendarService } from "@/ee/calendars/services/apple-calendar.service";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GoogleCalendarService } from "@/ee/calendars/services/gcal.service";
import { OutlookService } from "@/ee/calendars/services/outlook.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
@@ -21,13 +28,23 @@ import {
Headers,
Redirect,
BadRequestException,
Post,
Body,
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { User } from "@prisma/client";
import { plainToClass } from "class-transformer";
import { Request } from "express";
import { z } from "zod";
import { APPS_READ } from "@calcom/platform-constants";
import { SUCCESS_STATUS, CALENDARS, GOOGLE_CALENDAR, OFFICE_365_CALENDAR } from "@calcom/platform-constants";
import {
SUCCESS_STATUS,
CALENDARS,
GOOGLE_CALENDAR,
OFFICE_365_CALENDAR,
APPLE_CALENDAR,
} from "@calcom/platform-constants";
import { ApiResponse, CalendarBusyTimesInput } from "@calcom/platform-types";
@Controller({
@@ -39,10 +56,12 @@ export class CalendarsController {
constructor(
private readonly calendarsService: CalendarsService,
private readonly outlookService: OutlookService,
private readonly googleCalendarService: GoogleCalendarService
private readonly googleCalendarService: GoogleCalendarService,
private readonly appleCalendarService: AppleCalendarService,
private readonly calendarsRepository: CalendarsRepository
) {}
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
@Get("/busy-times")
async getBusyTimes(
@Query() queryParams: CalendarBusyTimesInput,
@@ -71,7 +90,7 @@ export class CalendarsController {
}
@Get("/")
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
async getCalendars(@GetUser("id") userId: number): Promise<ConnectedCalendarsOutput> {
const calendars = await this.calendarsService.getCalendars(userId);
@@ -81,7 +100,7 @@ export class CalendarsController {
};
}
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
@Get("/:calendar/connect")
@HttpCode(HttpStatus.OK)
async redirect(
@@ -133,16 +152,18 @@ export class CalendarsController {
}
}
@Get("/:calendar/check")
@HttpCode(HttpStatus.OK)
@UseGuards(AccessTokenGuard, PermissionsGuard)
@Permissions([APPS_READ])
async check(@GetUser("id") userId: number, @Param("calendar") calendar: string): Promise<ApiResponse> {
@UseGuards(ApiAuthGuard)
@Post("/:calendar/credentials")
async syncCredentials(
@GetUser() user: User,
@Param("calendar") calendar: string,
@Body() body: { username: string; password: string }
): Promise<{ status: string }> {
const { username, password } = body;
switch (calendar) {
case OFFICE_365_CALENDAR:
return await this.outlookService.check(userId);
case GOOGLE_CALENDAR:
return await this.googleCalendarService.check(userId);
case APPLE_CALENDAR:
return await this.appleCalendarService.save(user.id, user.email, username, password);
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
@@ -150,4 +171,49 @@ export class CalendarsController {
);
}
}
@Get("/:calendar/check")
@HttpCode(HttpStatus.OK)
@UseGuards(ApiAuthGuard, PermissionsGuard)
@Permissions([APPS_READ])
async check(@GetUser("id") userId: number, @Param("calendar") calendar: string): Promise<ApiResponse> {
switch (calendar) {
case OFFICE_365_CALENDAR:
return await this.outlookService.check(userId);
case GOOGLE_CALENDAR:
return await this.googleCalendarService.check(userId);
case APPLE_CALENDAR:
return await this.appleCalendarService.check(userId);
default:
throw new BadRequestException(
"Invalid calendar type, available calendars are: ",
CALENDARS.join(", ")
);
}
}
@UseGuards(ApiAuthGuard)
@Post("/:calendar/disconnect")
@HttpCode(HttpStatus.OK)
async deleteCalendarCredentials(
@Param("calendar") calendar: string,
@Body() body: DeleteCalendarCredentialsInputBodyDto,
@GetUser() user: UserWithProfile
): Promise<DeletedCalendarCredentialsOutputResponseDto> {
const { id: credentialId } = body;
await this.calendarsService.checkCalendarCredentials(credentialId, user.id);
const { id, type, userId, teamId, appId, invalid } = await this.calendarsRepository.deleteCredentials(
credentialId
);
return {
status: SUCCESS_STATUS,
data: plainToClass(
DeletedCalendarCredentialsOutputDto,
{ id, type, userId, teamId, appId, invalid },
{ strategy: "excludeAll" }
),
};
}
}
@@ -0,0 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import { Expose } from "class-transformer";
import { IsInt } from "class-validator";
export class DeleteCalendarCredentialsInputBodyDto {
@IsInt()
@Expose()
@ApiProperty({
example: 10,
description: "Credential ID of the calendar to delete, as returned by the /calendars endpoint",
type: "integer",
required: true,
})
readonly id!: number;
}
@@ -0,0 +1,42 @@
import { ApiProperty } from "@nestjs/swagger";
import { Expose, Type } from "class-transformer";
import { IsString, ValidateNested, IsEnum, IsInt, IsBoolean } from "class-validator";
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
export class DeletedCalendarCredentialsOutputDto {
@IsInt()
@Expose()
readonly id!: number;
@IsString()
@Expose()
readonly type!: string;
@IsInt()
@Expose()
readonly userId!: number | null;
@IsInt()
@Expose()
readonly teamId!: number | null;
@IsString()
@Expose()
readonly appId!: string | null;
@IsBoolean()
@Expose()
readonly invalid!: boolean | null;
}
export class DeletedCalendarCredentialsOutputResponseDto {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Expose()
@ValidateNested()
@Type(() => DeletedCalendarCredentialsOutputDto)
data!: DeletedCalendarCredentialsOutputDto;
}
@@ -0,0 +1,92 @@
import { CredentialSyncCalendarApp } from "@/ee/calendars/calendars.interface";
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { BadRequestException, UnauthorizedException } from "@nestjs/common";
import { Injectable } from "@nestjs/common";
import { SUCCESS_STATUS, APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
import { symmetricEncrypt, CalendarService } from "@calcom/platform-libraries-0.0.23";
@Injectable()
export class AppleCalendarService implements CredentialSyncCalendarApp {
constructor(
private readonly calendarsService: CalendarsService,
private readonly credentialRepository: CredentialsRepository
) {}
async save(
userId: number,
userEmail: string,
username: string,
password: string
): Promise<{ status: string }> {
return await this.saveCalendarCredentials(userId, userEmail, username, password);
}
async check(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
return await this.checkIfCalendarConnected(userId);
}
async checkIfCalendarConnected(userId: number): Promise<{ status: typeof SUCCESS_STATUS }> {
const appleCalendarCredentials = await this.credentialRepository.getByTypeAndUserId(
APPLE_CALENDAR_TYPE,
userId
);
if (!appleCalendarCredentials) {
throw new BadRequestException("Credentials for apple calendar not found.");
}
if (appleCalendarCredentials.invalid) {
throw new BadRequestException("Invalid apple calendar credentials.");
}
const { connectedCalendars } = await this.calendarsService.getCalendars(userId);
const appleCalendar = connectedCalendars.find(
(cal: { integration: { type: string } }) => cal.integration.type === APPLE_CALENDAR_TYPE
);
if (!appleCalendar) {
throw new UnauthorizedException("Apple calendar not connected.");
}
if (appleCalendar.error?.message) {
throw new UnauthorizedException(appleCalendar.error?.message);
}
return {
status: SUCCESS_STATUS,
};
}
async saveCalendarCredentials(userId: number, userEmail: string, username: string, password: string) {
if (username.length <= 1 || password.length <= 1)
throw new BadRequestException(`Username or password cannot be empty`);
const data = {
type: APPLE_CALENDAR_TYPE,
key: symmetricEncrypt(
JSON.stringify({ username, password }),
process.env.CALENDSO_ENCRYPTION_KEY || ""
),
userId: userId,
teamId: null,
appId: APPLE_CALENDAR_ID,
invalid: false,
};
try {
const dav = new CalendarService({
id: 0,
...data,
user: { email: userEmail },
});
await dav?.listCalendars();
await this.credentialRepository.createAppCredential(APPLE_CALENDAR_TYPE, data.key, userId);
} catch (reason) {
throw new BadRequestException(`Could not add this apple calendar account: ${reason}`);
}
return {
status: SUCCESS_STATUS,
};
}
}
@@ -1,3 +1,4 @@
import { CalendarsRepository } from "@/ee/calendars/calendars.repository";
import { AppsRepository } from "@/modules/apps/apps.repository";
import {
CredentialsRepository,
@@ -17,8 +18,7 @@ import { User } from "@prisma/client";
import { DateTime } from "luxon";
import { z } from "zod";
import { getConnectedDestinationCalendars } from "@calcom/platform-libraries-0.0.2";
import { getBusyCalendarTimes } from "@calcom/platform-libraries-0.0.2";
import { getConnectedDestinationCalendars, getBusyCalendarTimes } from "@calcom/platform-libraries-0.0.23";
import { Calendar } from "@calcom/platform-types";
import { PrismaClient } from "@calcom/prisma";
@@ -30,6 +30,7 @@ export class CalendarsService {
private readonly usersRepository: UsersRepository,
private readonly credentialsRepository: CredentialsRepository,
private readonly appsRepository: AppsRepository,
private readonly calendarsRepository: CalendarsRepository,
private readonly dbRead: PrismaReadService,
private readonly dbWrite: PrismaWriteService,
private readonly config: ConfigService
@@ -137,4 +138,11 @@ export class CalendarsService {
return { client_id, client_secret };
}
async checkCalendarCredentials(credentialId: number, userId: number) {
const credential = await this.calendarsRepository.getCalendarCredentials(credentialId, userId);
if (!credential) {
throw new NotFoundException("Calendar credentials not found");
}
}
}
@@ -1,17 +0,0 @@
import { EventTypesController } from "@/ee/event-types/controllers/event-types.controller";
import { EventTypesRepository } from "@/ee/event-types/event-types.repository";
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, MembershipsModule, TokensModule, UsersModule, SelectedCalendarsModule],
providers: [EventTypesRepository, EventTypesService],
controllers: [EventTypesController],
exports: [EventTypesService, EventTypesRepository],
})
export class EventTypesModule {}
@@ -1,13 +1,13 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { EventTypesModule } from "@/ee/event-types/event-types.module";
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
import { Editable } from "@/ee/event-types/inputs/enums/editable";
import { BaseField } from "@/ee/event-types/inputs/enums/field-type";
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output";
import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output";
import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output";
import { Editable } from "@/ee/event-types/event-types_2024_04_15//inputs/enums/editable";
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
@@ -22,15 +22,20 @@ import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-type
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withAccessTokenAuth } from "test/utils/withAccessTokenAuth";
import { withApiAuth } from "test/utils/withApiAuth";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import {
SUCCESS_STATUS,
VERSION_2024_06_11,
VERSION_2024_04_15,
CAL_API_VERSION_HEADER,
} from "@calcom/platform-constants";
import {
EventTypesByViewer,
EventTypesPublic,
eventTypeBookingFields,
eventTypeLocations,
} from "@calcom/platform-libraries-0.0.2";
} from "@calcom/platform-libraries-0.0.23";
import { ApiSuccessResponse } from "@calcom/platform-types";
describe("Event types Endpoints", () => {
@@ -40,7 +45,7 @@ describe("Event types Endpoints", () => {
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, EventTypesModule, TokensModule],
imports: [AppModule, UsersModule, EventTypesModule_2024_04_15, TokensModule],
})
.overrideGuard(PermissionsGuard)
.useValue({
@@ -79,11 +84,11 @@ describe("Event types Endpoints", () => {
let user: User;
beforeAll(async () => {
const moduleRef = await withAccessTokenAuth(
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, EventTypesModule, TokensModule],
imports: [AppModule, UsersModule, EventTypesModule_2024_04_15, TokensModule],
})
)
.overrideGuard(PermissionsGuard)
@@ -132,7 +137,7 @@ describe("Event types Endpoints", () => {
});
it("should create an event type", async () => {
const body: CreateEventTypeInput = {
const body: CreateEventTypeInput_2024_04_15 = {
title: "Test Event Type",
slug: "test-event-type",
description: "A description of the test event type.",
@@ -154,6 +159,7 @@ describe("Event types Endpoints", () => {
return request(app.getHttpServer())
.post("/api/v2/event-types")
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
.send(body)
.expect(201)
.then(async (response) => {
@@ -173,7 +179,7 @@ describe("Event types Endpoints", () => {
it("should update event type", async () => {
const newTitle = "Updated title";
const body: UpdateEventTypeInput = {
const body: UpdateEventTypeInput_2024_04_15 = {
title: newTitle,
disableGuests: false,
slotInterval: 30,
@@ -184,6 +190,7 @@ describe("Event types Endpoints", () => {
return request(app.getHttpServer())
.patch(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
.send(body)
.expect(200)
.then(async (response) => {
@@ -207,7 +214,7 @@ describe("Event types Endpoints", () => {
it("should return 400 if param event type id is null", async () => {
const locations = [{ type: "inPerson", address: "123 Main St" }];
const body: UpdateEventTypeInput = {
const body: UpdateEventTypeInput_2024_04_15 = {
locations,
};
@@ -217,7 +224,7 @@ describe("Event types Endpoints", () => {
it("should update event type locations", async () => {
const locations = [{ type: "inPerson", address: "123 Main St" }];
const body: UpdateEventTypeInput = {
const body: UpdateEventTypeInput_2024_04_15 = {
locations,
};
@@ -266,7 +273,7 @@ describe("Event types Endpoints", () => {
},
];
const body: UpdateEventTypeInput = {
const body: UpdateEventTypeInput_2024_04_15 = {
bookingFields,
};
@@ -288,7 +295,26 @@ describe("Event types Endpoints", () => {
it(`/GET/:id`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${eventType.id}`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
const responseBody: GetEventTypeOutput = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data.eventType.id).toEqual(eventType.id);
expect(responseBody.data.eventType.title).toEqual(eventType.title);
expect(responseBody.data.eventType.slug).toEqual(eventType.slug);
expect(responseBody.data.eventType.userId).toEqual(user.id);
});
it(`/GET/:id with version VERSION_2024_06_11`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_11)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
@@ -305,7 +331,8 @@ describe("Event types Endpoints", () => {
it(`/GET/:username/public`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${username}/public`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
@@ -323,7 +350,8 @@ describe("Event types Endpoints", () => {
it(`/GET/:username/:eventSlug/public`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${username}/${eventType.slug}/public`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
@@ -340,7 +368,8 @@ describe("Event types Endpoints", () => {
it(`/GET/`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
@@ -359,7 +388,8 @@ describe("Event types Endpoints", () => {
it(`/GET/public/:username/`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${username}/public`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
@@ -375,13 +405,17 @@ describe("Event types Endpoints", () => {
it(`/GET/:id not existing`, async () => {
await request(app.getHttpServer())
.get(`/api/v2/event-types/1000`)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
// note: bearer token value mocked using "withApiAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(404);
});
it("should delete schedule", async () => {
return request(app.getHttpServer()).delete(`/api/v2/event-types/${eventType.id}`).expect(200);
return request(app.getHttpServer())
.delete(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_04_15)
.expect(200);
});
afterAll(async () => {
@@ -1,19 +1,22 @@
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
import { EventTypeIdParams } from "@/ee/event-types/inputs/event-type-id.input";
import { GetPublicEventTypeQueryParams } from "@/ee/event-types/inputs/get-public-event-type-query-params.input";
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
import { CreateEventTypeOutput } from "@/ee/event-types/outputs/create-event-type.output";
import { DeleteEventTypeOutput } from "@/ee/event-types/outputs/delete-event-type.output";
import { GetEventTypePublicOutput } from "@/ee/event-types/outputs/get-event-type-public.output";
import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output";
import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output";
import { GetEventTypesData, GetEventTypesOutput } from "@/ee/event-types/outputs/get-event-types.output";
import { UpdateEventTypeOutput } from "@/ee/event-types/outputs/update-event-type.output";
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { EventTypeIdParams_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-id.input";
import { GetPublicEventTypeQueryParams_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/get-public-event-type-query-params.input";
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
import { CreateEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/create-event-type.output";
import { DeleteEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/delete-event-type.output";
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
import {
GetEventTypesData,
GetEventTypesOutput,
} from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types.output";
import { UpdateEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/update-event-type.output";
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
import { VERSION_2024_04_15, VERSION_2024_06_11 } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { UserWithProfile } from "@/modules/users/users.repository";
@@ -36,27 +39,26 @@ import {
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import { getPublicEvent } from "@calcom/platform-libraries-0.0.2";
import { getEventTypesByViewer } from "@calcom/platform-libraries-0.0.2";
import { getPublicEvent, getEventTypesByViewer } from "@calcom/platform-libraries-0.0.2";
import { PrismaClient } from "@calcom/prisma";
@Controller({
path: "/v2/event-types",
version: API_VERSIONS_VALUES,
version: [VERSION_2024_04_15, VERSION_2024_06_11],
})
@UseGuards(PermissionsGuard)
@DocsTags("Event types")
export class EventTypesController {
export class EventTypesController_2024_04_15 {
constructor(
private readonly eventTypesService: EventTypesService,
private readonly eventTypesService: EventTypesService_2024_04_15,
private readonly prismaReadService: PrismaReadService
) {}
@Post("/")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
async createEventType(
@Body() body: CreateEventTypeInput,
@Body() body: CreateEventTypeInput_2024_04_15,
@GetUser() user: UserWithProfile
): Promise<CreateEventTypeOutput> {
const eventType = await this.eventTypesService.createUserEventType(user, body);
@@ -69,11 +71,9 @@ export class EventTypesController {
@Get("/:eventTypeId")
@Permissions([EVENT_TYPE_READ])
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
async getEventType(
@Param() params: EventTypeIdParams,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@GetUser() user: UserWithProfile
): Promise<GetEventTypeOutput> {
const eventType = await this.eventTypesService.getUserEventTypeForAtom(user, Number(eventTypeId));
@@ -90,7 +90,7 @@ export class EventTypesController {
@Get("/")
@Permissions([EVENT_TYPE_READ])
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
async getEventTypes(@GetUser() user: UserWithProfile): Promise<GetEventTypesOutput> {
const eventTypes = await getEventTypesByViewer({
id: user.id,
@@ -109,7 +109,7 @@ export class EventTypesController {
async getPublicEventType(
@Param("username") username: string,
@Param("eventSlug") eventSlug: string,
@Query() queryParams: GetPublicEventTypeQueryParams
@Query() queryParams: GetPublicEventTypeQueryParams_2024_04_15
): Promise<GetEventTypePublicOutput> {
try {
const event = await getPublicEvent(
@@ -146,12 +146,12 @@ export class EventTypesController {
@Patch("/:eventTypeId")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
@HttpCode(HttpStatus.OK)
async updateEventType(
@Param() params: EventTypeIdParams,
@Param() params: EventTypeIdParams_2024_04_15,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@Body() body: UpdateEventTypeInput,
@Body() body: UpdateEventTypeInput_2024_04_15,
@GetUser() user: UserWithProfile
): Promise<UpdateEventTypeOutput> {
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
@@ -164,9 +164,9 @@ export class EventTypesController {
@Delete("/:eventTypeId")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(AccessTokenGuard)
@UseGuards(ApiAuthGuard)
async deleteEventType(
@Param() params: EventTypeIdParams,
@Param() params: EventTypeIdParams_2024_04_15,
@Param("eventTypeId", ParseIntPipe) eventTypeId: number,
@GetUser("id") userId: number
): Promise<DeleteEventTypeOutput> {
@@ -0,0 +1,17 @@
import { EventTypesController_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/controllers/event-types.controller";
import { EventTypesRepository_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.repository";
import { EventTypesService_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/services/event-types.service";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, MembershipsModule, TokensModule, UsersModule, SelectedCalendarsModule],
providers: [EventTypesRepository_2024_04_15, EventTypesService_2024_04_15],
controllers: [EventTypesController_2024_04_15],
exports: [EventTypesService_2024_04_15, EventTypesRepository_2024_04_15],
})
export class EventTypesModule_2024_04_15 {}
@@ -1,19 +1,19 @@
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import { getEventTypeById } from "@calcom/platform-libraries-0.0.2";
import { getEventTypeById } from "@calcom/platform-libraries-0.0.23";
import type { PrismaClient } from "@calcom/prisma";
@Injectable()
export class EventTypesRepository {
export class EventTypesRepository_2024_04_15 {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async createUserEventType(
userId: number,
body: Pick<CreateEventTypeInput, "title" | "slug" | "length" | "hidden">
body: Pick<CreateEventTypeInput_2024_04_15, "title" | "slug" | "length" | "hidden">
) {
return this.dbWrite.prisma.eventType.create({
data: {
@@ -1,4 +1,4 @@
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
import { ApiProperty as DocsProperty, ApiHideProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import {
@@ -20,7 +20,7 @@ export const CREATE_EVENT_DESCRIPTION_EXAMPLE =
// note(Lauris): We will gradually expose more properties if any customer needs them.
// Just uncomment any below when requested.
export class CreateEventTypeInput {
export class CreateEventTypeInput_2024_04_15 {
@IsNumber()
@Min(1)
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
@@ -46,9 +46,9 @@ export class CreateEventTypeInput {
@IsOptional()
@ValidateNested({ each: true })
@Type(() => EventTypeLocation)
@Type(() => EventTypeLocation_2024_04_15)
@IsArray()
locations?: EventTypeLocation[];
locations?: EventTypeLocation_2024_04_15[];
@IsBoolean()
@IsOptional()
@@ -1,6 +1,6 @@
import { IsNumberString } from "class-validator";
export class EventTypeIdParams {
export class EventTypeIdParams_2024_04_15 {
@IsNumberString()
eventTypeId!: number;
}
@@ -4,7 +4,7 @@ import { IsString, IsNumber, IsBoolean, IsOptional, IsUrl } from "class-validato
// note(Lauris): We will gradually expose more properties if any customer needs them.
// Just uncomment any below when requested.
export class EventTypeLocation {
export class EventTypeLocation_2024_04_15 {
@IsString()
@DocsProperty({ example: "link" })
type!: string;
@@ -2,7 +2,7 @@ import { ApiProperty } from "@nestjs/swagger";
import { Transform } from "class-transformer";
import { IsBoolean, IsOptional, IsString } from "class-validator";
export class GetPublicEventTypeQueryParams {
export class GetPublicEventTypeQueryParams_2024_04_15 {
@Transform(({ value }: { value: string }) => value === "true")
@IsBoolean()
@IsOptional()
@@ -1,7 +1,7 @@
import { Editable } from "@/ee/event-types/inputs/enums/editable";
import { BaseField } from "@/ee/event-types/inputs/enums/field-type";
import { Frequency } from "@/ee/event-types/inputs/enums/frequency";
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
import { Editable } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/editable";
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
import { Frequency } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/frequency";
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
import { Type } from "class-transformer";
import {
IsString,
@@ -105,7 +105,7 @@ class VariantsConfig {
variants!: Record<string, Variant>;
}
export class BookingField {
export class BookingField_2024_04_15 {
@IsEnum(BaseField)
type!: BaseField;
@@ -179,7 +179,7 @@ export class BookingField {
sources?: Source[];
}
export class RecurringEvent {
export class RecurringEvent_2024_04_15 {
@IsDate()
@IsOptional()
dtstart?: Date;
@@ -202,7 +202,7 @@ export class RecurringEvent {
tzid?: string;
}
export class IntervalLimits {
export class IntervalLimits_2024_04_15 {
@IsNumber()
@IsOptional()
PER_DAY?: number;
@@ -220,7 +220,7 @@ export class IntervalLimits {
PER_YEAR?: number;
}
export class UpdateEventTypeInput {
export class UpdateEventTypeInput_2024_04_15 {
@IsInt()
@Min(1)
@IsOptional()
@@ -243,9 +243,9 @@ export class UpdateEventTypeInput {
hidden?: boolean;
@ValidateNested({ each: true })
@Type(() => EventTypeLocation)
@Type(() => EventTypeLocation_2024_04_15)
@IsOptional()
locations?: EventTypeLocation[];
locations?: EventTypeLocation_2024_04_15[];
// @IsInt()
// @IsOptional()
@@ -278,8 +278,8 @@ export class UpdateEventTypeInput {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => BookingField)
bookingFields?: BookingField[];
@Type(() => BookingField_2024_04_15)
bookingFields?: BookingField_2024_04_15[];
// @IsString()
// @IsOptional()
@@ -1,4 +1,4 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
@@ -2,7 +2,7 @@ import {
CREATE_EVENT_LENGTH_EXAMPLE,
CREATE_EVENT_SLUG_EXAMPLE,
CREATE_EVENT_TITLE_EXAMPLE,
} from "@/ee/event-types/inputs/create-event-type.input";
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
@@ -3,15 +3,15 @@ import {
CREATE_EVENT_LENGTH_EXAMPLE,
CREATE_EVENT_SLUG_EXAMPLE,
CREATE_EVENT_TITLE_EXAMPLE,
} from "@/ee/event-types/inputs/create-event-type.input";
import { PeriodType } from "@/ee/event-types/inputs/enums/period-type";
import { SchedulingType } from "@/ee/event-types/inputs/enums/scheduling-type";
import { EventTypeLocation } from "@/ee/event-types/inputs/event-type-location.input";
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { PeriodType } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/period-type";
import { SchedulingType } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/scheduling-type";
import { EventTypeLocation_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/event-type-location.input";
import {
BookingField,
IntervalLimits,
RecurringEvent,
} from "@/ee/event-types/inputs/update-event-type.input";
BookingField_2024_04_15,
IntervalLimits_2024_04_15,
RecurringEvent_2024_04_15,
} from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
import { ApiProperty as DocsProperty, ApiHideProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import {
@@ -53,9 +53,9 @@ export class EventTypeOutput {
hidden!: boolean;
@ValidateNested({ each: true })
@Type(() => EventTypeLocation)
@Type(() => EventTypeLocation_2024_04_15)
@IsArray()
locations!: EventTypeLocation[] | null;
locations!: EventTypeLocation_2024_04_15[] | null;
@IsInt()
@ApiHideProperty()
@@ -91,9 +91,9 @@ export class EventTypeOutput {
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => BookingField)
@Type(() => BookingField_2024_04_15)
@ApiHideProperty()
bookingFields!: BookingField[] | null;
bookingFields!: BookingField_2024_04_15[] | null;
@IsString()
@ApiHideProperty()
@@ -132,10 +132,10 @@ export class EventTypeOutput {
requiresBookerEmailVerification!: boolean;
@ValidateNested()
@Type(() => RecurringEvent)
@Type(() => RecurringEvent_2024_04_15)
@IsOptional()
@ApiHideProperty()
recurringEvent!: RecurringEvent | null;
recurringEvent!: RecurringEvent_2024_04_15 | null;
@IsBoolean()
@ApiHideProperty()
@@ -203,15 +203,15 @@ export class EventTypeOutput {
successRedirectUrl!: string | null;
@ValidateNested()
@Type(() => IntervalLimits)
@Type(() => IntervalLimits_2024_04_15)
@IsOptional()
@ApiHideProperty()
bookingLimits!: IntervalLimits;
bookingLimits!: IntervalLimits_2024_04_15;
@ValidateNested()
@Type(() => IntervalLimits)
@Type(() => IntervalLimits_2024_04_15)
@ApiHideProperty()
durationLimits!: IntervalLimits;
durationLimits!: IntervalLimits_2024_04_15;
@IsBoolean()
@ApiHideProperty()
@@ -1,4 +1,4 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, ValidateNested } from "class-validator";
@@ -2,7 +2,7 @@ import {
CREATE_EVENT_LENGTH_EXAMPLE,
CREATE_EVENT_SLUG_EXAMPLE,
CREATE_EVENT_TITLE_EXAMPLE,
} from "@/ee/event-types/inputs/create-event-type.input";
} from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
@@ -1,4 +1,4 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsArray, IsEnum, ValidateNested } from "class-validator";
@@ -1,4 +1,4 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
@@ -1,29 +1,36 @@
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/constants/constants";
import { EventTypesRepository } from "@/ee/event-types/event-types.repository";
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_04_15/constants/constants";
import { EventTypesRepository_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.repository";
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
import { EventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/event-type.output";
import { MembershipsRepository } from "@/modules/memberships/memberships.repository";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { SelectedCalendarsRepository } from "@/modules/selected-calendars/selected-calendars.repository";
import { UserWithProfile, UsersRepository } from "@/modules/users/users.repository";
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
import { createEventType, updateEventType } from "@calcom/platform-libraries-0.0.2";
import { getEventTypesPublic, EventTypesPublic } from "@calcom/platform-libraries-0.0.2";
import {
createEventType,
updateEventType,
EventTypesPublic,
getEventTypesPublic,
} from "@calcom/platform-libraries-0.0.23";
import { EventType } from "@calcom/prisma/client";
@Injectable()
export class EventTypesService {
export class EventTypesService_2024_04_15 {
constructor(
private readonly eventTypesRepository: EventTypesRepository,
private readonly eventTypesRepository: EventTypesRepository_2024_04_15,
private readonly membershipsRepository: MembershipsRepository,
private readonly usersRepository: UsersRepository,
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
private readonly dbWrite: PrismaWriteService
) {}
async createUserEventType(user: UserWithProfile, body: CreateEventTypeInput): Promise<EventTypeOutput> {
async createUserEventType(
user: UserWithProfile,
body: CreateEventTypeInput_2024_04_15
): Promise<EventTypeOutput> {
await this.checkCanCreateEventType(user.id, body);
const eventTypeUser = await this.getUserToCreateEvent(user);
const { eventType } = await createEventType({
@@ -38,7 +45,7 @@ export class EventTypesService {
return eventType as EventTypeOutput;
}
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput) {
async checkCanCreateEventType(userId: number, body: CreateEventTypeInput_2024_04_15) {
const existsWithSlug = await this.eventTypesRepository.getUserEventTypeBySlug(userId, body.slug);
if (existsWithSlug) {
throw new BadRequestException("User already has an event type with this slug.");
@@ -53,6 +60,7 @@ export class EventTypesService {
const profileId = user.movedToProfile?.id || null;
return {
id: user.id,
role: user.role,
organizationId: user.organizationId,
organization: { isOrgAdmin },
profile: { id: profileId },
@@ -114,7 +122,7 @@ export class EventTypesService {
return defaultEventTypes;
}
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput, user: UserWithProfile) {
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput_2024_04_15, user: UserWithProfile) {
this.checkCanUpdateEventType(user.id, eventTypeId);
const eventTypeUser = await this.getUserToUpdateEvent(user);
await updateEventType({
@@ -0,0 +1,16 @@
export const DEFAULT_EVENT_TYPES = {
thirtyMinutes: { length: 30, slug: "thirty-minutes", title: "30 Minutes" },
thirtyMinutesVideo: {
length: 30,
slug: "thirty-minutes-video",
title: "30 Minutes",
locations: [{ type: "integrations:daily" }],
},
sixtyMinutes: { length: 60, slug: "sixty-minutes", title: "60 Minutes" },
sixtyMinutesVideo: {
length: 60,
slug: "sixty-minutes-video",
title: "60 Minutes",
locations: [{ type: "integrations:daily" }],
},
};
@@ -0,0 +1,383 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import { PlatformOAuthClient, Team, User, Schedule } from "@prisma/client";
import * as request from "supertest";
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withApiAuth } from "test/utils/withApiAuth";
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
import {
ApiSuccessResponse,
CreateEventTypeInput_2024_06_14,
EventTypeOutput_2024_06_14,
UpdateEventTypeInput_2024_06_14,
} from "@calcom/platform-types";
describe("Event types Endpoints", () => {
describe("Not authenticated", () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
})
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
await app.init();
});
it(`/GET/:id`, () => {
return request(app.getHttpServer()).get("/api/v2/event-types/100").expect(401);
});
afterAll(async () => {
await app.close();
});
});
describe("User Authenticated", () => {
let app: INestApplication;
let oAuthClient: PlatformOAuthClient;
let organization: Team;
let userRepositoryFixture: UserRepositoryFixture;
let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
let teamRepositoryFixture: TeamRepositoryFixture;
let eventTypesRepositoryFixture: EventTypesRepositoryFixture;
let schedulesRepostoryFixture: SchedulesRepositoryFixture;
const userEmail = "event-types-test-e2e@api.com";
const falseTestEmail = "false-event-types@api.com";
const name = "bob-the-builder";
const username = name;
let eventType: EventTypeOutput_2024_06_14;
let user: User;
let falseTestUser: User;
let firstSchedule: Schedule;
let secondSchedule: Schedule;
let falseTestSchedule: Schedule;
beforeAll(async () => {
const moduleRef = await withApiAuth(
userEmail,
Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
imports: [AppModule, UsersModule, EventTypesModule_2024_06_14, TokensModule],
})
)
.overrideGuard(PermissionsGuard)
.useValue({
canActivate: () => true,
})
.compile();
app = moduleRef.createNestApplication();
bootstrap(app as NestExpressApplication);
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
eventTypesRepositoryFixture = new EventTypesRepositoryFixture(moduleRef);
schedulesRepostoryFixture = new SchedulesRepositoryFixture(moduleRef);
organization = await teamRepositoryFixture.create({ name: "organization" });
oAuthClient = await createOAuthClient(organization.id);
user = await userRepositoryFixture.create({
email: userEmail,
name,
username,
});
falseTestUser = await userRepositoryFixture.create({
email: falseTestEmail,
name: "false-test",
username: falseTestEmail,
});
firstSchedule = await schedulesRepostoryFixture.create({
userId: user.id,
name: "work",
timeZone: "Europe/Rome",
});
secondSchedule = await schedulesRepostoryFixture.create({
userId: user.id,
name: "chill",
timeZone: "Europe/Rome",
});
falseTestSchedule = await schedulesRepostoryFixture.create({
userId: falseTestUser.id,
name: "work",
timeZone: "Europe/Rome",
});
await app.init();
});
async function createOAuthClient(organizationId: number) {
const data = {
logo: "logo-url",
name: "name",
redirectUris: ["redirect-uri"],
permissions: 32,
};
const secret = "secret";
const client = await oauthClientRepositoryFixture.create(organizationId, data, secret);
return client;
}
it("should be defined", () => {
expect(oauthClientRepositoryFixture).toBeDefined();
expect(userRepositoryFixture).toBeDefined();
expect(oAuthClient).toBeDefined();
expect(user).toBeDefined();
});
it("should not allow creating an event type with schedule user does not own", async () => {
const scheduleId = falseTestSchedule.id;
const body: CreateEventTypeInput_2024_06_14 = {
title: "Coding class",
slug: "coding-class",
description: "Let's learn how to code like a pro.",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
bookingFields: [
{
type: "select",
label: "select which language you want to learn",
slug: "select-language",
required: true,
placeholder: "select language",
options: ["javascript", "python", "cobol"],
},
],
scheduleId,
};
return request(app.getHttpServer())
.post("/api/v2/event-types")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(404);
});
it("should create an event type", async () => {
const body: CreateEventTypeInput_2024_06_14 = {
title: "Coding class",
slug: "coding-class",
description: "Let's learn how to code like a pro.",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
bookingFields: [
{
type: "select",
label: "select which language you want to learn",
slug: "select-language",
required: true,
placeholder: "select language",
options: ["javascript", "python", "cobol"],
},
],
scheduleId: firstSchedule.id,
};
return request(app.getHttpServer())
.post("/api/v2/event-types")
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(201)
.then(async (response) => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const createdEventType = responseBody.data;
expect(createdEventType).toHaveProperty("id");
expect(createdEventType.title).toEqual(body.title);
expect(createdEventType.description).toEqual(body.description);
expect(createdEventType.lengthInMinutes).toEqual(body.lengthInMinutes);
expect(createdEventType.locations).toEqual(body.locations);
expect(createdEventType.bookingFields).toEqual(body.bookingFields);
expect(createdEventType.ownerId).toEqual(user.id);
expect(createdEventType.scheduleId).toEqual(firstSchedule.id);
eventType = responseBody.data;
});
});
it("should update event type", async () => {
const newTitle = "Coding class in Italian!";
const body: UpdateEventTypeInput_2024_06_14 = {
title: newTitle,
scheduleId: secondSchedule.id,
};
return request(app.getHttpServer())
.patch(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(200)
.then(async (response) => {
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const updatedEventType = responseBody.data;
expect(updatedEventType.title).toEqual(body.title);
expect(updatedEventType.id).toEqual(eventType.id);
expect(updatedEventType.title).toEqual(newTitle);
expect(updatedEventType.description).toEqual(eventType.description);
expect(updatedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
expect(updatedEventType.locations).toEqual(eventType.locations);
expect(updatedEventType.bookingFields).toEqual(eventType.bookingFields);
expect(updatedEventType.ownerId).toEqual(user.id);
expect(updatedEventType.scheduleId).toEqual(secondSchedule.id);
eventType.title = newTitle;
eventType.scheduleId = secondSchedule.id;
});
});
it("should not allow to update event type with scheduleId user does not own", async () => {
const body: UpdateEventTypeInput_2024_06_14 = {
scheduleId: falseTestSchedule.id,
};
return request(app.getHttpServer())
.patch(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
.send(body)
.expect(404);
});
it(`/GET/:id`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types/${eventType.id}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14> = response.body;
const fetchedEventType = responseBody.data;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(fetchedEventType.id).toEqual(eventType.id);
expect(fetchedEventType.title).toEqual(eventType.title);
expect(fetchedEventType.description).toEqual(eventType.description);
expect(fetchedEventType.lengthInMinutes).toEqual(eventType.lengthInMinutes);
expect(fetchedEventType.locations).toEqual(eventType.locations);
expect(fetchedEventType.bookingFields).toEqual(eventType.bookingFields);
expect(fetchedEventType.ownerId).toEqual(user.id);
});
it(`/GET/even-types by username`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types?username=${username}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data?.length).toEqual(1);
const fetchedEventType = responseBody.data?.[0];
expect(fetchedEventType?.id).toEqual(eventType.id);
expect(fetchedEventType?.title).toEqual(eventType.title);
expect(fetchedEventType?.description).toEqual(eventType.description);
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
expect(fetchedEventType?.locations).toEqual(eventType.locations);
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
expect(fetchedEventType?.ownerId).toEqual(user.id);
});
it(`/GET/event-types by username and eventSlug`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/event-types?username=${username}&eventSlug=${eventType.slug}`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(200);
const responseBody: ApiSuccessResponse<EventTypeOutput_2024_06_14[]> = response.body;
const fetchedEventType = responseBody.data[0];
expect(fetchedEventType?.id).toEqual(eventType.id);
expect(fetchedEventType?.title).toEqual(eventType.title);
expect(fetchedEventType?.description).toEqual(eventType.description);
expect(fetchedEventType?.lengthInMinutes).toEqual(eventType.lengthInMinutes);
expect(fetchedEventType?.locations).toEqual(eventType.locations);
expect(fetchedEventType?.bookingFields).toEqual(eventType.bookingFields);
expect(fetchedEventType?.ownerId).toEqual(user.id);
});
it(`/GET/:id not existing`, async () => {
await request(app.getHttpServer())
.get(`/api/v2/event-types/1000`)
.set(CAL_API_VERSION_HEADER, VERSION_2024_06_14)
// note: bearer token value mocked using "withAccessTokenAuth" for user which id is used when creating event type above
.set("Authorization", `Bearer whatever`)
.expect(404);
});
it("should delete event type", async () => {
return request(app.getHttpServer()).delete(`/api/v2/event-types/${eventType.id}`).expect(200);
});
afterAll(async () => {
await oauthClientRepositoryFixture.delete(oAuthClient.id);
await teamRepositoryFixture.delete(organization.id);
try {
await eventTypesRepositoryFixture.delete(eventType.id);
} catch (e) {
// Event type might have been deleted by the test
}
try {
await userRepositoryFixture.delete(user.id);
} catch (e) {
// User might have been deleted by the test
}
try {
await userRepositoryFixture.delete(falseTestUser.id);
} catch (e) {
// User might have been deleted by the test
}
await app.close();
});
});
});
@@ -0,0 +1,127 @@
import { CreateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/create-event-type.output";
import { DeleteEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/delete-event-type.output";
import { GetEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-type.output";
import { GetEventTypesOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/get-event-types.output";
import { UpdateEventTypeOutput_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/outputs/update-event-type.output";
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
import { VERSION_2024_06_14_VALUE } from "@/lib/api-versions";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Controller,
UseGuards,
Get,
Param,
Post,
Body,
NotFoundException,
Patch,
HttpCode,
HttpStatus,
Delete,
Query,
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import {
CreateEventTypeInput_2024_06_14,
UpdateEventTypeInput_2024_06_14,
GetEventTypesQuery_2024_06_14,
} from "@calcom/platform-types";
@Controller({
path: "/v2/event-types",
version: VERSION_2024_06_14_VALUE,
})
@UseGuards(PermissionsGuard)
@DocsTags("Event types")
export class EventTypesController_2024_06_14 {
constructor(private readonly eventTypesService: EventTypesService_2024_06_14) {}
@Post("/")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(ApiAuthGuard)
async createEventType(
@Body() body: CreateEventTypeInput_2024_06_14,
@GetUser() user: UserWithProfile
): Promise<CreateEventTypeOutput_2024_06_14> {
const eventType = await this.eventTypesService.createUserEventType(user, body);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Get("/:eventTypeId")
@Permissions([EVENT_TYPE_READ])
@UseGuards(ApiAuthGuard)
async getEventTypeById(
@Param("eventTypeId") eventTypeId: string,
@GetUser() user: UserWithProfile
): Promise<GetEventTypeOutput_2024_06_14> {
const eventType = await this.eventTypesService.getUserEventType(user.id, Number(eventTypeId));
if (!eventType) {
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
}
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Get("/")
async getEventTypes(
@Query() queryParams: GetEventTypesQuery_2024_06_14
): Promise<GetEventTypesOutput_2024_06_14> {
const eventTypes = await this.eventTypesService.getEventTypes(queryParams);
return {
status: SUCCESS_STATUS,
data: eventTypes,
};
}
@Patch("/:eventTypeId")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(ApiAuthGuard)
@HttpCode(HttpStatus.OK)
async updateEventType(
@Param("eventTypeId") eventTypeId: number,
@Body() body: UpdateEventTypeInput_2024_06_14,
@GetUser() user: UserWithProfile
): Promise<UpdateEventTypeOutput_2024_06_14> {
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
return {
status: SUCCESS_STATUS,
data: eventType,
};
}
@Delete("/:eventTypeId")
@Permissions([EVENT_TYPE_WRITE])
@UseGuards(ApiAuthGuard)
async deleteEventType(
@Param("eventTypeId") eventTypeId: number,
@GetUser("id") userId: number
): Promise<DeleteEventTypeOutput_2024_06_14> {
const eventType = await this.eventTypesService.deleteEventType(eventTypeId, userId);
return {
status: SUCCESS_STATUS,
data: {
id: eventType.id,
lengthInMinutes: eventType.length,
slug: eventType.slug,
title: eventType.title,
},
};
}
}
@@ -0,0 +1,34 @@
import { EventTypesController_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/controllers/event-types.controller";
import { EventTypesRepository_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.repository";
import { EventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/event-types.service";
import { InputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/input-event-types.service";
import { OutputEventTypesService_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/services/output-event-types.service";
import { SchedulesRepository_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.repository";
import { MembershipsModule } from "@/modules/memberships/memberships.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { SelectedCalendarsModule } from "@/modules/selected-calendars/selected-calendars.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersService } from "@/modules/users/services/users.service";
import { UsersRepository } from "@/modules/users/users.repository";
import { Module } from "@nestjs/common";
@Module({
imports: [PrismaModule, MembershipsModule, TokensModule, SelectedCalendarsModule],
providers: [
EventTypesRepository_2024_06_14,
EventTypesService_2024_06_14,
InputEventTypesService_2024_06_14,
OutputEventTypesService_2024_06_14,
UsersRepository,
UsersService,
SchedulesRepository_2024_06_11,
],
controllers: [EventTypesController_2024_06_14],
exports: [
EventTypesService_2024_06_14,
EventTypesRepository_2024_06_14,
InputEventTypesService_2024_06_14,
OutputEventTypesService_2024_06_14,
],
})
export class EventTypesModule_2024_06_14 {}
@@ -0,0 +1,103 @@
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import {
getEventTypeById,
transformApiEventTypeBookingFields,
transformApiEventTypeLocations,
} from "@calcom/platform-libraries-0.0.23";
import { CreateEventTypeInput_2024_06_14 } from "@calcom/platform-types";
import type { PrismaClient } from "@calcom/prisma";
type InputEventTransformed = Omit<
CreateEventTypeInput_2024_06_14,
"lengthInMinutes" | "locations" | "bookingFields"
> & {
length: number;
slug: string;
locations?: ReturnType<typeof transformApiEventTypeLocations>;
bookingFields?: ReturnType<typeof transformApiEventTypeBookingFields>;
};
@Injectable()
export class EventTypesRepository_2024_06_14 {
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
async createUserEventType(userId: number, body: InputEventTransformed) {
return this.dbWrite.prisma.eventType.create({
data: {
...body,
userId,
locations: body.locations,
bookingFields: body.bookingFields,
users: { connect: { id: userId } },
},
});
}
async getEventTypeWithSeats(eventTypeId: number) {
return this.dbRead.prisma.eventType.findUnique({
where: { id: eventTypeId },
select: { users: { select: { id: true } }, seatsPerTimeSlot: true },
});
}
async getUserEventType(userId: number, eventTypeId: number) {
return this.dbRead.prisma.eventType.findFirst({
where: {
id: eventTypeId,
userId,
},
include: { users: true, schedule: true },
});
}
async getUserEventTypes(userId: number) {
return this.dbRead.prisma.eventType.findMany({
where: {
userId,
},
include: { users: true, schedule: true },
});
}
async getUserEventTypeForAtom(
user: UserWithProfile,
isUserOrganizationAdmin: boolean,
eventTypeId: number
) {
return await getEventTypeById({
currentOrganizationId: user.movedToProfile?.organizationId || user.organizationId,
eventTypeId,
userId: user.id,
prisma: this.dbRead.prisma as unknown as PrismaClient,
isUserOrganizationAdmin,
isTrpcCall: true,
});
}
async getEventTypeById(eventTypeId: number) {
return this.dbRead.prisma.eventType.findUnique({
where: { id: eventTypeId },
include: { users: true, schedule: true },
});
}
async getUserEventTypeBySlug(userId: number, slug: string) {
return this.dbRead.prisma.eventType.findUnique({
where: {
userId_slug: {
userId: userId,
slug: slug,
},
},
include: { users: true, schedule: true },
});
}
async deleteEventType(eventTypeId: number) {
return this.dbWrite.prisma.eventType.delete({ where: { id: eventTypeId } });
}
}
@@ -0,0 +1,20 @@
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsIn, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { EventTypeOutput_2024_06_14 } from "@calcom/platform-types";
export class CreateEventTypeOutput_2024_06_14 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: EventTypeOutput_2024_06_14,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => EventTypeOutput_2024_06_14)
data!: EventTypeOutput_2024_06_14;
}
@@ -0,0 +1,33 @@
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsIn, IsInt, IsString } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
import { CREATE_EVENT_LENGTH_EXAMPLE, CREATE_EVENT_TITLE_EXAMPLE } from "@calcom/platform-types";
class DeleteData_2024_06_14 {
@IsInt()
@DocsProperty({ example: 1 })
id!: number;
@IsInt()
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
lengthInMinutes!: number;
@IsString()
slug!: string;
@IsString()
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
title!: string;
}
export class DeleteEventTypeOutput_2024_06_14 {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsIn([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Type(() => DeleteData_2024_06_14)
data!: DeleteData_2024_06_14;
}

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