Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75d5576e17 | ||
|
|
0a6dde4fcc | ||
|
|
73b5839284 | ||
|
|
1a42b94b13 | ||
|
|
acb910f680 | ||
|
|
38e857f9a9 | ||
|
|
b8149dee36 | ||
|
|
f583ef40f2 | ||
|
|
71574488c5 | ||
|
|
008e0e43a2 | ||
|
|
da22eda662 | ||
|
|
f94d696885 | ||
|
|
9e84eb1b2c | ||
|
|
4c549811a1 | ||
|
|
05502f0cfb | ||
|
|
e436b8e06b | ||
|
|
8146cf8c93 | ||
|
|
c05f595ef0 | ||
|
|
0a795f5363 | ||
|
|
5b50a469b2 | ||
|
|
c08365f09c | ||
|
|
477e102388 | ||
|
|
e1b10b4ec5 | ||
|
|
ff6db3c572 | ||
|
|
4fa3fdef97 | ||
|
|
f29f6632e8 | ||
|
|
36747988e6 | ||
|
|
3dd312b88b | ||
|
|
3c9e9329b0 | ||
|
|
8617f43a8e | ||
|
|
5f0eb652b0 | ||
|
|
26d7301885 | ||
|
|
ffc4d22ff1 | ||
|
|
ee286a600f | ||
|
|
72c493ee21 | ||
|
|
5bd0428d18 | ||
|
|
c7089b9db6 | ||
|
|
81ad00a816 | ||
|
|
b5730da56d | ||
|
|
9455d14ad5 | ||
|
|
a056160404 | ||
|
|
6d53cd84f8 | ||
|
|
30cb8f745d | ||
|
|
bafa3c97ac | ||
|
|
bb48ccbad9 | ||
|
|
0a68c6d048 | ||
|
|
bb9f8e5d38 | ||
|
|
c99bb190f1 | ||
|
|
f0d2a45fdf | ||
|
|
e5f9315ee6 | ||
|
|
f7782b2080 | ||
|
|
e1f2dbab84 | ||
|
|
c2edbbcea7 | ||
|
|
c7e78cc181 | ||
|
|
7ba1557d1d | ||
|
|
d5b45b757a | ||
|
|
bb3b262bd7 | ||
|
|
d4f41e803a | ||
|
|
74eafa59e1 | ||
|
|
e6cd016cad | ||
|
|
7b0d5959cb | ||
|
|
7702ded85e | ||
|
|
5acb3e8f97 | ||
|
|
41046aaf27 | ||
|
|
7cffb30daa | ||
|
|
26bacb6c8e | ||
|
|
01b5c1f11e | ||
|
|
f5ba04599b | ||
|
|
994e602c8d | ||
|
|
a776c72d48 | ||
|
|
6adb5b413d | ||
|
|
b183a5779a | ||
|
|
16c1cf7b23 | ||
|
|
cf4ee142de | ||
|
|
a5d99bbec9 | ||
|
|
5d0468a986 | ||
|
|
7fef9db4ce | ||
|
|
296a367955 | ||
|
|
1ff2d51ee5 | ||
|
|
092cb0f691 | ||
|
|
0c74a98721 | ||
|
|
2082a4f4ed | ||
|
|
f06ef84f61 | ||
|
|
1080d7f88f | ||
|
|
4ebd52c9af | ||
|
|
e0ac95578c | ||
|
|
473012f2c0 | ||
|
|
1293588ac4 | ||
|
|
c0c40185fc | ||
|
|
3348e7be2f | ||
|
|
97dcb2a2a9 | ||
|
|
5dad78f8fe | ||
|
|
88cc83a0aa | ||
|
|
9dddf5e525 | ||
|
|
9df9f9bb6b | ||
|
|
c4803be323 | ||
|
|
34d360a4ec | ||
|
|
0e6f44e2b9 | ||
|
|
e377997d49 | ||
|
|
a0451e34c1 | ||
|
|
d2b4760e3a | ||
|
|
8469c91973 | ||
|
|
0990549e7f | ||
|
|
e884348807 | ||
|
|
5684ce2d28 | ||
|
|
bd8ac12f14 | ||
|
|
512628e8ab | ||
|
|
903a154dbc | ||
|
|
1a3d54a0d0 | ||
|
|
a5970cb1a8 | ||
|
|
925dc04f1c | ||
|
|
271f31730d | ||
|
|
684f4a795c | ||
|
|
836429348c | ||
|
|
a55138348f | ||
|
|
66b5dfd796 | ||
|
|
bba68e09a9 | ||
|
|
bab182960f | ||
|
|
f09153b3bb | ||
|
|
3e492eb17b | ||
|
|
fe5fe8d53b | ||
|
|
0cc5937104 | ||
|
|
fde3b49126 | ||
|
|
556787b93b | ||
|
|
dcb244d7be | ||
|
|
8d7fb75f71 | ||
|
|
4fe75771b8 | ||
|
|
63f537a08f | ||
|
|
bed593c835 | ||
|
|
f367c5ed3d | ||
|
|
af1382c5ba | ||
|
|
eeb0fc795d | ||
|
|
4aa040d96e | ||
|
|
12466fb064 | ||
|
|
42a3448abb | ||
|
|
92ff49f6a1 | ||
|
|
c4aa555610 | ||
|
|
a45d77de51 | ||
|
|
0421dd401a | ||
|
|
094189fc3b | ||
|
|
677a964d4f | ||
|
|
2357c919c1 | ||
|
|
27b7cccfa6 | ||
|
|
ab2354f2a4 | ||
|
|
538e5017c5 | ||
|
|
2e3060b738 | ||
|
|
e9d9aebd88 | ||
|
|
7d1c69801e | ||
|
|
4235b32349 | ||
|
|
2ca031e25c | ||
|
|
9f734b8964 | ||
|
|
d6927c5049 | ||
|
|
910f5f906f | ||
|
|
71f955e256 | ||
|
|
5e60654aa9 | ||
|
|
b6175b3dde | ||
|
|
a0a731b357 | ||
|
|
fdaa6961f8 | ||
|
|
d91e74ec13 | ||
|
|
0af780aac9 | ||
|
|
41b0589ab0 | ||
|
|
00e96ac7ad | ||
|
|
63feb721ea | ||
|
|
43c1bd63ca | ||
|
|
8bc265faaa | ||
|
|
f33ea91ab0 | ||
|
|
cc0ff17d97 | ||
|
|
4d128971bb | ||
|
|
f9f82283fb | ||
|
|
111135a605 | ||
|
|
04e577582d | ||
|
|
6643d3c20b | ||
|
|
3c42301c19 | ||
|
|
20517be280 | ||
|
|
fa45a85088 | ||
|
|
c839e40a3e | ||
|
|
9422e28598 | ||
|
|
d804b3947d | ||
|
|
3fa710fc1c | ||
|
|
f9359aaa7b | ||
|
|
7c7ca79d48 | ||
|
|
753cb5803d | ||
|
|
210bc9fc8c | ||
|
|
3d8b5390df | ||
|
|
a477f337dc | ||
|
|
9db9204088 | ||
|
|
fe46b81c63 | ||
|
|
17db644be1 | ||
|
|
e4037be79a | ||
|
|
ebe6a60366 | ||
|
|
fa9c6ea0ef | ||
|
|
9b22f44cba | ||
|
|
c84f75df99 | ||
|
|
8f9a4fc78f | ||
|
|
7b30ade284 | ||
|
|
24a4646fdc | ||
|
|
0860036607 | ||
|
|
d080a0ef2d | ||
|
|
a6eab74fd1 | ||
|
|
0a2e275908 | ||
|
|
3b05ebc5b5 | ||
|
|
e5b4d2fb80 | ||
|
|
00a2a3d8a8 | ||
|
|
30ba168fc3 | ||
|
|
12eec186b2 | ||
|
|
4417d649ea | ||
|
|
7058da70e6 | ||
|
|
2cb383c337 | ||
|
|
fe5eca8eb3 | ||
|
|
7c6468953a | ||
|
|
28a0553d18 | ||
|
|
ffcb04fa99 | ||
|
|
5ca216b922 | ||
|
|
0abff9f562 | ||
|
|
942dc38b58 | ||
|
|
827d8995a1 | ||
|
|
850e222f0e | ||
|
|
ede2357203 | ||
|
|
559ab4192f | ||
|
|
3c8c6d0574 | ||
|
|
2ae2c15083 | ||
|
|
313bb89789 | ||
|
|
95e037c664 | ||
|
|
da026d909d | ||
|
|
c6b50bfd14 | ||
|
|
30b433397a | ||
|
|
ff1aefe539 | ||
|
|
5685d343b1 | ||
|
|
ae3f30fabf | ||
|
|
c11e76bac9 | ||
|
|
eed34559b5 | ||
|
|
bf958c82da | ||
|
|
728a386bf0 | ||
|
|
41efb5ea04 | ||
|
|
b268540d35 | ||
|
|
bdd1d3a99d | ||
|
|
dd7673b1f9 | ||
|
|
29f37a7e98 | ||
|
|
3983351dac | ||
|
|
01322431fc | ||
|
|
dcd06b0663 | ||
|
|
26e50d24b0 | ||
|
|
74803dc122 | ||
|
|
de65b85862 | ||
|
|
0ee56be1d8 | ||
|
|
de6f7dcfc0 | ||
|
|
956f2c7a2b | ||
|
|
14de0a1d62 | ||
|
|
51cca7481f | ||
|
|
3fe0280dee | ||
|
|
876fc188d4 | ||
|
|
d1810d9f14 | ||
|
|
a25cd43ddf | ||
|
|
833a91375e | ||
|
|
8124627cc5 | ||
|
|
749747ad7e | ||
|
|
967f70a5ec | ||
|
|
51c9b94fb5 | ||
|
|
1976dd15c3 | ||
|
|
03671e2a58 | ||
|
|
5e5f223908 | ||
|
|
9076a7eb42 | ||
|
|
a9e50d76ca | ||
|
|
bd4722554d | ||
|
|
105d373b8e | ||
|
|
df91dacc3f | ||
|
|
396b81af58 | ||
|
|
8467a9c53b | ||
|
|
e99e8b169a | ||
|
|
7b8902a769 | ||
|
|
2408338ed4 | ||
|
|
8f0ff58bcf | ||
|
|
fac47544d1 | ||
|
|
b09f3e5c8d | ||
|
|
8bb051ac4e | ||
|
|
fd3fffd7dd | ||
|
|
312a3f2543 | ||
|
|
f52f2f32fa | ||
|
|
fdd0a5a03d | ||
|
|
ca3e3600f7 | ||
|
|
ba944b471a | ||
|
|
7978f0317a | ||
|
|
d918f34ae8 | ||
|
|
1fed08fa9b | ||
|
|
aabfd3b3f6 | ||
|
|
60e0bbf4b7 | ||
|
|
0a7f65cd9b | ||
|
|
f41f4b01a9 | ||
|
|
a7b38b1b58 | ||
|
|
a0f1ceba19 | ||
|
|
5c6e104ff5 | ||
|
|
29b1f7bf51 | ||
|
|
7ec3d04acc | ||
|
|
13a765e50a |
No files matched your search
+12
-2
@@ -23,6 +23,9 @@ CALCOM_LICENSE_KEY=
|
|||||||
|
|
||||||
# - DATABASE ************************************************************************************************
|
# - DATABASE ************************************************************************************************
|
||||||
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
|
DATABASE_URL="postgresql://postgres:@localhost:5450/calendso"
|
||||||
|
# Needed to run migrations while using a connection pooler like PgBouncer
|
||||||
|
# Use the same one as DATABASE_URL if you're not using a connection pooler
|
||||||
|
DATABASE_DIRECT_URL="postgresql://postgres:@localhost:5450/calendso"
|
||||||
UPSTASH_REDIS_REST_URL=
|
UPSTASH_REDIS_REST_URL=
|
||||||
UPSTASH_REDIS_REST_TOKEN=
|
UPSTASH_REDIS_REST_TOKEN=
|
||||||
INSIGHTS_DATABASE_URL=
|
INSIGHTS_DATABASE_URL=
|
||||||
@@ -134,10 +137,15 @@ NEXT_PUBLIC_SENDGRID_SENDER_NAME=
|
|||||||
NEXT_PUBLIC_SENTRY_DSN=
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
|
||||||
# Formbricks Experience Management Integration
|
# Formbricks Experience Management Integration
|
||||||
FORMBRICKS_HOST_URL=https://app.formbricks.com
|
NEXT_PUBLIC_FORMBRICKS_HOST_URL=https://app.formbricks.com
|
||||||
FORMBRICKS_ENVIRONMENT_ID=
|
NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID=
|
||||||
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
||||||
|
|
||||||
|
# AvatarAPI
|
||||||
|
# Used to pre-fill avatar during signup
|
||||||
|
AVATARAPI_USERNAME=
|
||||||
|
AVATARAPI_PASSWORD=
|
||||||
|
|
||||||
# Twilio
|
# Twilio
|
||||||
# Used to send SMS reminders in workflows
|
# Used to send SMS reminders in workflows
|
||||||
TWILIO_SID=
|
TWILIO_SID=
|
||||||
@@ -231,6 +239,8 @@ CSP_POLICY=
|
|||||||
EDGE_CONFIG=
|
EDGE_CONFIG=
|
||||||
|
|
||||||
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
|
NEXT_PUBLIC_MINUTES_TO_BOOK=5 # Minutes
|
||||||
|
NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD=0 # Override the booker to only load X number of days worth of data
|
||||||
|
|
||||||
# Control time intervals on a user's Schedule availability
|
# Control time intervals on a user's Schedule availability
|
||||||
NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL=
|
NEXT_PUBLIC_AVAILABILITY_SCHEDULE_INTERVAL=
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ runs:
|
|||||||
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
key-1: ${{ hashFiles('packages/prisma/schema.prisma', 'packages/prisma/migrations/**/**.sql', 'packages/prisma/*.ts') }}
|
||||||
key-2: ${{ github.event.pull_request.number || github.ref }}
|
key-2: ${{ github.event.pull_request.number || github.ref }}
|
||||||
DATABASE_URL: ${{ inputs.DATABASE_URL }}
|
DATABASE_URL: ${{ inputs.DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ inputs.DATABASE_URL }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ inputs.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ inputs.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ inputs.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -28,6 +34,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
@@ -35,6 +45,7 @@ jobs:
|
|||||||
- uses: ./.github/actions/cache-db
|
- uses: ./.github/actions/cache-db
|
||||||
env:
|
env:
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
E2E_TEST_CALCOM_QA_PASSWORD: ${{ secrets.E2E_TEST_CALCOM_QA_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS: ${{ secrets.E2E_TEST_CALCOM_QA_GCAL_CREDENTIALS }}
|
||||||
@@ -46,6 +57,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -23,6 +26,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
@@ -37,6 +44,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -28,6 +34,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
@@ -40,6 +50,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
@@ -18,6 +21,9 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
mailhog:
|
mailhog:
|
||||||
image: mailhog/mailhog:v1.0.1
|
image: mailhog/mailhog:v1.0.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
ports:
|
ports:
|
||||||
- 8025:8025
|
- 8025:8025
|
||||||
- 1025:1025
|
- 1025:1025
|
||||||
@@ -26,6 +32,10 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5]
|
shard: [1, 2, 3, 4, 5]
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
@@ -38,6 +48,7 @@ jobs:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
DEPLOYSENTINEL_API_KEY: ${{ secrets.DEPLOYSENTINEL_API_KEY }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
repo-token: ${{ secrets.EQUITY_BEE_TEAM_LABELER_ACTION_TOKEN }}
|
||||||
organization-name: calcom
|
organization-name: calcom
|
||||||
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, platform, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
ignore-labels: "admin, app-store, ai, authentication, automated-testing, devops, billing, bookings, caldav, calendar-apps, ci, console, crm-apps, docs, documentation, emails, embeds, event-types, i18n, impersonation, manual-testing, ui, performance, ops-stack, organizations, public-api, routing-forms, seats, teams, webhooks, workflows, zapier"
|
||||||
apply-labels-from-issue:
|
apply-labels-from-issue:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
body="${body//'%'/'%25'}"
|
body="${body//'%'/'%25'}"
|
||||||
body="${body//$'\n'/'%0A'}"
|
body="${body//$'\n'/'%0A'}"
|
||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
echo "{body}=${body}" >> $GITHUB_OUTPUT
|
echo ::set-output name=body::$body
|
||||||
|
|
||||||
- name: Find Comment
|
- name: Find Comment
|
||||||
uses: peter-evans/find-comment@v2
|
uses: peter-evans/find-comment@v2
|
||||||
|
|||||||
@@ -12,14 +12,6 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
login:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
changes:
|
changes:
|
||||||
name: Detect changes
|
name: Detect changes
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ env:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
GOOGLE_API_CREDENTIALS: ${{ secrets.CI_GOOGLE_API_CREDENTIALS }}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ env:
|
|||||||
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
ALLOWED_HOSTNAMES: ${{ vars.CI_ALLOWED_HOSTNAMES }}
|
||||||
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
CALENDSO_ENCRYPTION_KEY: ${{ secrets.CI_CALENDSO_ENCRYPTION_KEY }}
|
||||||
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
DATABASE_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
|
DATABASE_DIRECT_URL: ${{ secrets.CI_DATABASE_URL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
E2E_TEST_APPLE_CALENDAR_EMAIL: ${{ secrets.E2E_TEST_APPLE_CALENDAR_EMAIL }}
|
||||||
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
E2E_TEST_APPLE_CALENDAR_PASSWORD: ${{ secrets.E2E_TEST_APPLE_CALENDAR_PASSWORD }}
|
||||||
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
E2E_TEST_CALCOM_QA_EMAIL: ${{ secrets.E2E_TEST_CALCOM_QA_EMAIL }}
|
||||||
@@ -41,6 +42,9 @@ jobs:
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:12.1
|
image: postgres:12.1
|
||||||
|
credentials:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: calendso
|
POSTGRES_DB: calendso
|
||||||
|
|||||||
+226
-56
@@ -1,9 +1,10 @@
|
|||||||
// This transform config was generated by Snaplet.
|
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
|
||||||
// Snaplet found fields that may contain personally identifiable information (PII)
|
/// <reference path=".snaplet/snaplet.d.ts" />
|
||||||
// and used that to populate this file.
|
// This config was generated by Snaplet make sure to check it over before using it.
|
||||||
import { copycat as c } from "@snaplet/copycat";
|
import { copycat as c } from "@snaplet/copycat";
|
||||||
|
import { defineConfig } from "snaplet";
|
||||||
|
|
||||||
import type { Transform } from "./structure";
|
// c.setHashKey(REPLACE_ME_WITH_YOUR_HASH_KEY);
|
||||||
|
|
||||||
function hasStringProp<T extends string>(x: unknown, key: T): x is { [key in T]: string } {
|
function hasStringProp<T extends string>(x: unknown, key: T): x is { [key in T]: string } {
|
||||||
return !!x && typeof x === "object" && key in x;
|
return !!x && typeof x === "object" && key in x;
|
||||||
@@ -41,57 +42,226 @@ function replaceSensitiveKeys(record: object) {
|
|||||||
|
|
||||||
const generateUsername = (x: string) => `${c.firstName(x)}-${c.lastName(x)}${c.int(x, { min: 2, max: 99 })}`;
|
const generateUsername = (x: string) => `${c.firstName(x)}-${c.lastName(x)}${c.int(x, { min: 2, max: 99 })}`;
|
||||||
|
|
||||||
const config: Transform = () => ({
|
export default defineConfig({
|
||||||
public: {
|
transform: {
|
||||||
ApiKey: ({ row }) => ({
|
$mode: "unsafe",
|
||||||
hashedKey: c.uuid(row.hashedKey),
|
public: {
|
||||||
}),
|
Account({ row }) {
|
||||||
App: ({ row }) => ({
|
return {
|
||||||
keys: replaceSensitiveKeys(row.keys),
|
refresh_token: c.uuid(row.refresh_token),
|
||||||
}),
|
access_token: c.uuid(row.access_token),
|
||||||
Attendee: ({ row }) => ({
|
expires_at: c.int(row.expires_at, {
|
||||||
email: c.email(row.email),
|
min: 0,
|
||||||
name: c.fullName(row.name),
|
max: Math.pow(4, 8) - 1,
|
||||||
timeZone: c.timezone(row.timeZone),
|
}),
|
||||||
}),
|
token_type: c.uuid(row.token_type),
|
||||||
Credential: ({ row }) => ({
|
id_token: c.uuid(row.id_token),
|
||||||
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
|
};
|
||||||
}),
|
},
|
||||||
EventType: ({ row }) => ({
|
ApiKey: ({ row }) => ({
|
||||||
slug: generateSlug(row.slug),
|
hashedKey: c.uuid(row.hashedKey),
|
||||||
timeZone: c.timezone(row.timeZone),
|
note: c.fullName(row.note),
|
||||||
eventName: c.words(row.eventName, { max: 3 }),
|
createdAt: c.dateString(row.createdAt, {
|
||||||
}),
|
minYear: 2020,
|
||||||
ResetPasswordRequest: ({ row }) => ({
|
}),
|
||||||
email: c.email(row.email),
|
}),
|
||||||
}),
|
App: ({ row }) => ({
|
||||||
Schedule: ({ row }) => ({
|
keys: replaceSensitiveKeys(row.keys),
|
||||||
timeZone: c.timezone(row.timeZone),
|
}),
|
||||||
}),
|
App_RoutingForms_Form({ row }) {
|
||||||
Team: ({ row }) => ({
|
return {
|
||||||
bio: c.sentence(row.bio),
|
name: c.fullName(row.name),
|
||||||
name: c.words(row.name, { max: 2 }),
|
};
|
||||||
slug: generateSlug(row.slug),
|
},
|
||||||
}),
|
Attendee: ({ row }) => ({
|
||||||
users: ({ row }) =>
|
email: c.email(row.email),
|
||||||
row.role !== "ADMIN"
|
name: c.fullName(row.name),
|
||||||
? {
|
timeZone: c.timezone(row.timeZone),
|
||||||
bio: c.sentence(row.bio),
|
locale: c.fullName(row.locale),
|
||||||
email: c.email(row.email),
|
}),
|
||||||
name: c.fullName(row.name),
|
Availability({ row }) {
|
||||||
password: c.password(row.password),
|
return {
|
||||||
timeZone: c.timezone(row.timeZone),
|
startTime: c
|
||||||
username: generateUsername(row.username),
|
.dateString(row.startTime, {
|
||||||
}
|
minYear: 2020,
|
||||||
: row,
|
})
|
||||||
VerificationToken: ({ row }) => ({
|
.slice(11, 19),
|
||||||
token: c.uuid(row.token),
|
endTime: c
|
||||||
}),
|
.dateString(row.endTime, {
|
||||||
Account: ({ row }) => ({
|
minYear: 2020,
|
||||||
access_token: c.uuid(row.access_token),
|
})
|
||||||
refresh_token: c.uuid(row.refresh_token),
|
.slice(11, 19),
|
||||||
}),
|
};
|
||||||
|
},
|
||||||
|
Booking({ row }) {
|
||||||
|
return {
|
||||||
|
title: c.fullName(row.title),
|
||||||
|
startTime: c.dateString(row.startTime, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
endTime: c.dateString(row.endTime, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
location: c.sentence(row.location),
|
||||||
|
metadata: {
|
||||||
|
[c.word(row.metadata)]: c.words(row.metadata),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Credential: ({ row }) => ({
|
||||||
|
key: typeof row.key === "string" ? c.uuid(row.key) : replaceSensitiveKeys(row.key),
|
||||||
|
}),
|
||||||
|
EventType: ({ row }) => ({
|
||||||
|
slug: generateSlug(row.slug),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
eventName: c.words(row.eventName, { max: 3 }),
|
||||||
|
currency: c.sentence(row.currency),
|
||||||
|
}),
|
||||||
|
EventTypeCustomInput({ row }) {
|
||||||
|
return {
|
||||||
|
label: c.fullName(row.label),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Feature({ row }) {
|
||||||
|
return {
|
||||||
|
slug: c.uuid(row.slug),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
InstantMeetingToken({ row }) {
|
||||||
|
return {
|
||||||
|
token: c.uuid(row.token),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
OAuthClient({ row }) {
|
||||||
|
return {
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
OutOfOfficeEntry({ row }) {
|
||||||
|
return {
|
||||||
|
start: c.dateString(row.start, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Payment({ row }) {
|
||||||
|
return {
|
||||||
|
amount: c.int(row.amount, {
|
||||||
|
min: 0,
|
||||||
|
max: Math.pow(4, 8) - 1,
|
||||||
|
}),
|
||||||
|
currency: c.sentence(row.currency),
|
||||||
|
data: {
|
||||||
|
[c.word(row.data)]: c.words(row.data),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
ResetPasswordRequest: ({ row }) => ({
|
||||||
|
email: c.email(row.email),
|
||||||
|
}),
|
||||||
|
Schedule: ({ row }) => ({
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
}),
|
||||||
|
Session({ row }) {
|
||||||
|
return {
|
||||||
|
sessionToken: c.uuid(row.sessionToken),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Team: ({ row }) => ({
|
||||||
|
bio: c.sentence(row.bio),
|
||||||
|
name: c.words(row.name, { max: 2 }),
|
||||||
|
slug: generateSlug(row.slug),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
logoUrl: c.username(row.logoUrl),
|
||||||
|
}),
|
||||||
|
TempOrgRedirect({ row }) {
|
||||||
|
return {
|
||||||
|
from: c.dateString(row.from, {
|
||||||
|
maxYear: 1999,
|
||||||
|
}),
|
||||||
|
toUrl: c.city(row.toUrl),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
VerificationToken({ row }) {
|
||||||
|
return {
|
||||||
|
id: c
|
||||||
|
.int(row.id, {
|
||||||
|
min: 1,
|
||||||
|
max: Math.pow(4, 8) - 1,
|
||||||
|
})
|
||||||
|
.toString(),
|
||||||
|
identifier: c.uuid(row.identifier),
|
||||||
|
token: c.uuid(row.token),
|
||||||
|
expires: c.dateString(row.expires, {
|
||||||
|
minYear: 2020,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
VerifiedNumber({ row }) {
|
||||||
|
return {
|
||||||
|
phoneNumber: c.phoneNumber(row.phoneNumber),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Webhook({ row }) {
|
||||||
|
return {
|
||||||
|
subscriberUrl: c.url(row.subscriberUrl),
|
||||||
|
secret: c.streetAddress(row.secret),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
WebhookScheduledTriggers({ row }) {
|
||||||
|
return {
|
||||||
|
jobName: c.fullName(row.jobName),
|
||||||
|
payload: c.password(row.payload),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
WorkflowStep({ row }) {
|
||||||
|
return {
|
||||||
|
sendTo: c.oneOf(row.sendTo, [
|
||||||
|
"Man",
|
||||||
|
"Woman",
|
||||||
|
"Transgender",
|
||||||
|
"Non-binary/non-conforming",
|
||||||
|
"Not specified",
|
||||||
|
]),
|
||||||
|
sender: c.oneOf(row.sender, [
|
||||||
|
"Man",
|
||||||
|
"Woman",
|
||||||
|
"Transgender",
|
||||||
|
"Non-binary/non-conforming",
|
||||||
|
"Not specified",
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
users: ({ row }) =>
|
||||||
|
row.role !== "ADMIN"
|
||||||
|
? {
|
||||||
|
bio: c.sentence(row.bio),
|
||||||
|
email: c.email(row.email),
|
||||||
|
name: c.fullName(row.name),
|
||||||
|
password: c.password(row.password),
|
||||||
|
timeZone: c.timezone(row.timeZone),
|
||||||
|
username: generateUsername(row.username),
|
||||||
|
metadata: {
|
||||||
|
[c.word(row.metadata)]: c.words(row.metadata),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: row,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
subset: {
|
||||||
|
enabled: true,
|
||||||
|
version: "3",
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
table: "public.users",
|
||||||
|
rowLimit: 100,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
keepDisconnectedTables: false,
|
||||||
|
followNullableRelations: true,
|
||||||
|
maxCyclesLoop: 0,
|
||||||
|
eager: false,
|
||||||
|
taskSortAlgorithm: "children",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default config;
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
Contact: security@cal.com
|
||||||
|
Preferred-Languages: en
|
||||||
|
Canonical: https://cal.com/.well-known/security.txt
|
||||||
|
Policy: https://github.com/calcom/cal.com/security/policy
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
diff --git a/plugin/timezone.js b/plugin/timezone.js
|
||||||
|
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
|
||||||
|
--- a/plugin/timezone.js
|
||||||
|
+++ b/plugin/timezone.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
|
||||||
|
\ No newline at end of file
|
||||||
|
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
diff --git a/plugin/timezone.js b/plugin/timezone.js
|
||||||
|
index fb6112a96f03f53ba78162ac323dce17f635161d..991e4e5410a32f5a69cb6e0e9476a9c529657aae 100644
|
||||||
|
--- a/plugin/timezone.js
|
||||||
|
+++ b/plugin/timezone.js
|
||||||
|
@@ -1 +1 @@
|
||||||
|
-!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";var t={year:0,month:1,day:2,hour:3,minute:4,second:5},e={};return function(n,i,o){var r,a=function(t,n,i){void 0===i&&(i={});var o=new Date(t),r=function(t,n){void 0===n&&(n={});var i=n.timeZoneName||"short",o=t+"|"+i,r=e[o];return r||(r=new Intl.DateTimeFormat("en-US",{hour12:!1,timeZone:t,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:i}),e[o]=r),r}(n,i);return r.formatToParts(o)},u=function(e,n){for(var i=a(e,n),r=[],u=0;u<i.length;u+=1){var f=i[u],s=f.type,m=f.value,c=t[s];c>=0&&(r[c]=parseInt(m,10))}var d=r[3],l=24===d?0:d,v=r[0]+"-"+r[1]+"-"+r[2]+" "+l+":"+r[4]+":"+r[5]+":000",h=+e;return(o.utc(v).valueOf()-(h-=h%1e3))/6e4},f=i.prototype;f.tz=function(t,e){void 0===t&&(t=r);var n=this.utcOffset(),i=this.toDate(),a=i.toLocaleString("en-US",{timeZone:t}),u=Math.round((i-new Date(a))/1e3/60),f=o(a).$set("millisecond",this.$ms).utcOffset(15*-Math.round(i.getTimezoneOffset()/15)-u,!0);if(e){var s=f.utcOffset();f=f.add(n-s,"minute")}return f.$x.$timezone=t,f},f.offsetName=function(t){var e=this.$x.$timezone||o.tz.guess(),n=a(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var s=f.startOf;f.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return s.call(this,t,e);var n=o(this.format("YYYY-MM-DD HH:mm:ss:SSS"));return s.call(n,t,e).tz(this.$x.$timezone,!0)},o.tz=function(t,e,n){var i=n&&e,a=n||e||r,f=u(+o(),a);if("string"!=typeof t)return o(t).tz(a);var s=function(t,e,n){var i=t-60*e*1e3,o=u(i,n);if(e===o)return[i,e];var r=u(i-=60*(o-e)*1e3,n);return o===r?[i,o]:[t-60*Math.min(o,r)*1e3,Math.max(o,r)]}(o.utc(t,i).valueOf(),f,a),m=s[0],c=s[1],d=o(m).utcOffset(c);return d.$x.$timezone=a,d},o.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},o.tz.setDefault=function(t){r=t}}}));
|
||||||
|
\ No newline at end of file
|
||||||
|
+!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_timezone=e()}(this,(function(){"use strict";function t(){return t=Object.assign?Object.assign.bind():function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(t[i]=n[i])}return t},t.apply(this,arguments)}var e={year:0,month:1,day:2,hour:3,minute:4,second:5},n={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"},i={},r={};return function(o,a,u){var f,s=function(e,r,o){void 0===o&&(o={});var a=new Date(e),u=function(e,r){void 0===r&&(r={});var o=r.timeZoneName||"short",a=e+"|"+o,u=i[a];return u||(u=new Intl.DateTimeFormat("en-US",t({},n,{hour12:!1,timeZone:e,timeZoneName:o})),i[a]=u),u}(r,o);return u.formatToParts(a)},m=function(t,n){for(var i=s(t,n),r=[],o=0;o<i.length;o+=1){var a=i[o],f=a.type,m=a.value,c=e[f];c>=0&&(r[c]=parseInt(m,10))}var l=r[3],d=24===l?0:l,h=r[0]+"-"+r[1]+"-"+r[2]+" "+d+":"+r[4]+":"+r[5]+":000",v=+t;return(u.utc(h).valueOf()-(v-=v%1e3))/6e4},c=a.prototype;c.tz=function(e,i){void 0===e&&(e=f);var o=this.utcOffset(),a=this.toDate(),s=function(e){var i=r[e];return i||(i=new Intl.DateTimeFormat("en-US",t({},n,{timeZone:e})),r[e]=i),i}(e).format(a),m=Math.round((a-new Date(s))/1e3/60),c=u(s,{locale:this.$L}).$set("millisecond",this.$ms).utcOffset(15*-Math.round(a.getTimezoneOffset()/15)-m,!0);if(i){var l=c.utcOffset();c=c.add(o-l,"minute")}return c.$x.$timezone=e,c},c.offsetName=function(t){var e=this.$x.$timezone||u.tz.guess(),n=s(this.valueOf(),e,{timeZoneName:t}).find((function(t){return"timezonename"===t.type.toLowerCase()}));return n&&n.value};var l=c.startOf;c.startOf=function(t,e){if(!this.$x||!this.$x.$timezone)return l.call(this,t,e);var n=u(this.format("YYYY-MM-DD HH:mm:ss:SSS"),{locale:this.$L});return l.call(n,t,e).tz(this.$x.$timezone,!0)},u.tz=function(t,e,n){var i=n&&e,r=n||e||f,o=m(+u(),r);if("string"!=typeof t)return u(t).tz(r);var a=function(t,e,n){var i=t-60*e*1e3,r=m(i,n);if(e===r)return[i,e];var o=m(i-=60*(r-e)*1e3,n);return r===o?[i,r]:[t-60*Math.min(r,o)*1e3,Math.max(r,o)]}(u.utc(t,i).valueOf(),o,r),s=a[0],c=a[1],l=u(s).utcOffset(c);return l.$x.$timezone=r,l},u.tz.guess=function(){return Intl.DateTimeFormat().resolvedOptions().timeZone},u.tz.setDefault=function(t){f=t}}}));
|
||||||
@@ -448,7 +448,7 @@ Don't code but still want to contribute? Join our [discord](https://go.cal.com/d
|
|||||||
3. Enable the selected API.
|
3. Enable the selected API.
|
||||||
4. Next, go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page.
|
4. Next, go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page.
|
||||||
5. In the second page on Scopes, select Add or Remove Scopes. Search for Calendar.event and select the scope with scope value `.../auth/calendar.events`, `.../auth/calendar.readonly` and select Update.
|
5. In the second page on Scopes, select Add or Remove Scopes. Search for Calendar.event and select the scope with scope value `.../auth/calendar.events`, `.../auth/calendar.readonly` and select Update.
|
||||||
6. In the third page (Test Users), add the Google account(s) you'll using. Make sure the details are correct on the last page of the wizard and your consent screen will be configured.
|
6. In the third page (Test Users), add the Google account(s) you'll be using. Make sure the details are correct on the last page of the wizard and your consent screen will be configured.
|
||||||
7. Now select [Credentials](https://console.cloud.google.com/apis/credentials) from the side pane and then select Create Credentials. Select the OAuth Client ID option.
|
7. Now select [Credentials](https://console.cloud.google.com/apis/credentials) from the side pane and then select Create Credentials. Select the OAuth Client ID option.
|
||||||
8. Select Web Application as the Application Type.
|
8. Select Web Application as the Application Type.
|
||||||
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ test.describe("Org", () => {
|
|||||||
const response = await page.goto("https://i.cal.com/embed");
|
const response = await page.goto("https://i.cal.com/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await page.screenshot({ path: "screenshot.jpg" });
|
await page.screenshot({ path: "screenshot.jpg" });
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
test("Org User(Peer) Page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/peer/embed");
|
const response = await page.goto("https://i.cal.com/peer/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
|
await expect(page.locator("text=Peer Richelsen")).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
test("Org User Event(peer/meet) Page should be embeddable", async ({ page }) => {
|
||||||
@@ -23,14 +23,14 @@ test.describe("Org", () => {
|
|||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
|
test("Org Team Profile(/sales) page should be embeddable", async ({ page }) => {
|
||||||
const response = await page.goto("https://i.cal.com/sales/embed");
|
const response = await page.goto("https://i.cal.com/sales/embed");
|
||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
|
await expect(page.locator("text=Cal.com Sales")).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
|
test("Org Team Event page(/sales/hippa) should be embeddable", async ({ page }) => {
|
||||||
@@ -38,9 +38,10 @@ test.describe("Org", () => {
|
|||||||
expect(response?.status()).toBe(200);
|
expect(response?.status()).toBe(200);
|
||||||
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="decrementMonth"]')).toBeVisible();
|
||||||
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
await expect(page.locator('[data-testid="incrementMonth"]')).toBeVisible();
|
||||||
await expectPageToBeServerSideRendered(page);
|
await expectPageToBeRenderedWithEmbedSsr(page);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe("Dynamic Group Booking", () => {
|
test.describe("Dynamic Group Booking", () => {
|
||||||
test("Dynamic Group booking link should load", async ({ page }) => {
|
test("Dynamic Group booking link should load", async ({ page }) => {
|
||||||
const users = [
|
const users = [
|
||||||
@@ -63,12 +64,39 @@ test.describe("Org", () => {
|
|||||||
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
expect((await page.locator('[data-testid="event-meta"] [data-testid="avatar"]').all()).length).toBe(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Organization Homepage - Has Engineering and Marketing Teams", async ({ page }) => {
|
||||||
|
const response = await page.goto("https://i.cal.com");
|
||||||
|
expect(response?.status()).toBe(200);
|
||||||
|
await expect(page.locator("text=Cal.com")).toBeVisible();
|
||||||
|
await expect(page.locator("text=Engineering")).toBeVisible();
|
||||||
|
await expect(page.locator("text=Marketing")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe("Browse the Engineering Team", async () => {
|
||||||
|
test("By User Navigation", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com");
|
||||||
|
await page.click('text="Engineering"');
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("By /team/engineering", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com/team/engineering");
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("By /engineering", async ({ page }) => {
|
||||||
|
await page.goto("https://i.cal.com/engineering");
|
||||||
|
await expect(page.locator("text=Cal.com Engineering")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
|
// This ensures that the route is actually mapped to a page that is using withEmbedSsr
|
||||||
async function expectPageToBeServerSideRendered(page: Page) {
|
async function expectPageToBeRenderedWithEmbedSsr(page: Page) {
|
||||||
expect(
|
expect(
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
|
//@ts-expect-error - __NEXT_DATA__ is a global variable defined by Next.js
|
||||||
return window.__NEXT_DATA__.props.pageProps.isEmbed;
|
return window.__NEXT_DATA__.props.pageProps.isEmbed;
|
||||||
})
|
})
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|||||||
@@ -5,12 +5,14 @@
|
|||||||
"author": "Cal.com Inc.",
|
"author": "Cal.com Inc.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@calcom/prisma": "*",
|
"@calcom/prisma": "*",
|
||||||
|
"@langchain/core": "^0.1.26",
|
||||||
|
"@langchain/openai": "^0.0.14",
|
||||||
"@t3-oss/env-nextjs": "^0.6.1",
|
"@t3-oss/env-nextjs": "^0.6.1",
|
||||||
"langchain": "^0.0.131",
|
"langchain": "^0.1.17",
|
||||||
"mailparser": "^3.6.5",
|
"mailparser": "^3.6.5",
|
||||||
"next": "^13.5.4",
|
"next": "^13.5.4",
|
||||||
"supports-color": "8.1.1",
|
"supports-color": "8.1.1",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mailparser": "^3.4.0"
|
"@types/mailparser": "^3.4.0"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { UserList } from "~/src/types/user";
|
import type { UserList } from "~/src/types/user";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
@@ -22,6 +22,8 @@ const fetchBookings = async ({
|
|||||||
const params = {
|
const params = {
|
||||||
apiKey,
|
apiKey,
|
||||||
userId: userId.toString(),
|
userId: userId.toString(),
|
||||||
|
dateFrom: from,
|
||||||
|
dateTo: to,
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(params);
|
const urlParams = new URLSearchParams(params);
|
||||||
@@ -40,11 +42,9 @@ const fetchBookings = async ({
|
|||||||
|
|
||||||
const bookings: Booking[] = data.bookings
|
const bookings: Booking[] = data.bookings
|
||||||
.filter((booking: Booking) => {
|
.filter((booking: Booking) => {
|
||||||
const afterFrom = new Date(booking.startTime).getTime() > new Date(from).getTime();
|
|
||||||
const beforeTo = new Date(booking.endTime).getTime() < new Date(to).getTime();
|
|
||||||
const notCancelled = booking.status !== BOOKING_STATUS.CANCELLED;
|
const notCancelled = booking.status !== BOOKING_STATUS.CANCELLED;
|
||||||
|
|
||||||
return afterFrom && beforeTo && notCancelled;
|
return notCancelled;
|
||||||
})
|
})
|
||||||
.map(({ endTime, eventTypeId, id, startTime, status, title }: Booking) => ({
|
.map(({ endTime, eventTypeId, id, startTime, status, title }: Booking) => ({
|
||||||
endTime,
|
endTime,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "~/src/env.mjs";
|
import { env } from "~/src/env.mjs";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DynamicStructuredTool } from "langchain/tools";
|
import { DynamicStructuredTool } from "@langchain/core/tools";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { ChatOpenAI } from "@langchain/openai";
|
||||||
import { initializeAgentExecutorWithOptions } from "langchain/agents";
|
import { initializeAgentExecutorWithOptions } from "langchain/agents";
|
||||||
import { ChatOpenAI } from "langchain/chat_models/openai";
|
|
||||||
|
|
||||||
import { env } from "../env.mjs";
|
import { env } from "../env.mjs";
|
||||||
import createBookingIfAvailable from "../tools/createBooking";
|
import createBookingIfAvailable from "../tools/createBooking";
|
||||||
@@ -13,7 +13,7 @@ import type { User, UserList } from "../types/user";
|
|||||||
import type { WorkingHours } from "../types/workingHours";
|
import type { WorkingHours } from "../types/workingHours";
|
||||||
import now from "./now";
|
import now from "./now";
|
||||||
|
|
||||||
const gptModel = "gpt-4";
|
const gptModel = "gpt-4-0125-preview";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
||||||
@@ -58,7 +58,14 @@ If you can't find a referenced user, ask the user for their email or @username.
|
|||||||
|
|
||||||
The primary user's id is: ${userId}
|
The primary user's id is: ${userId}
|
||||||
The primary user's username is: ${user.username}
|
The primary user's username is: ${user.username}
|
||||||
The current time in the primary user's timezone is: ${now(user.timeZone)}
|
The current time in the primary user's timezone is: ${now(user.timeZone, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
})}
|
||||||
The primary user's time zone is: ${user.timeZone}
|
The primary user's time zone is: ${user.timeZone}
|
||||||
The primary user's event types are: ${user.eventTypes
|
The primary user's event types are: ${user.eventTypes
|
||||||
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
|
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export default function now(timeZone: string) {
|
export default function now(timeZone: string, options: Intl.DateTimeFormatOptions = {}) {
|
||||||
return new Date().toLocaleString("en-US", {
|
return new Date().toLocaleString("en-US", {
|
||||||
timeZone,
|
timeZone,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,12 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"../../packages/types/window.d.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { captureException as SentryCaptureException } from "@sentry/nextjs";
|
||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { redactError } from "@calcom/lib/redactError";
|
import { redactError } from "@calcom/lib/redactError";
|
||||||
@@ -9,7 +9,7 @@ export const captureErrors: NextMiddleware = async (_req, res, next) => {
|
|||||||
// middleware and the API route handler
|
// middleware and the API route handler
|
||||||
await next();
|
await next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Sentry.captureException(error);
|
SentryCaptureException(error);
|
||||||
const redactedError = redactError(error);
|
const redactedError = redactError(error);
|
||||||
if (redactedError instanceof Error) {
|
if (redactedError instanceof Error) {
|
||||||
res.status(400).json({ message: redactedError.message, error: redactedError });
|
res.status(400).json({ message: redactedError.message, error: redactedError });
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { NextMiddleware } from "next-api-middleware";
|
import type { NextMiddleware } from "next-api-middleware";
|
||||||
|
|
||||||
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
|
||||||
|
import { API_KEY_RATE_LIMIT } from "@calcom/lib/rateLimit";
|
||||||
|
|
||||||
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
||||||
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
|
||||||
@@ -9,6 +10,11 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
|
|||||||
await checkRateLimitAndThrowError({
|
await checkRateLimitAndThrowError({
|
||||||
identifier: req.query.apiKey as string,
|
identifier: req.query.apiKey as string,
|
||||||
rateLimitingType: "api",
|
rateLimitingType: "api",
|
||||||
|
onRateLimiterResponse: (response) => {
|
||||||
|
res.setHeader("X-RateLimit-Limit", API_KEY_RATE_LIMIT);
|
||||||
|
res.setHeader("X-RateLimit-Remaining", response.remaining);
|
||||||
|
res.setHeader("X-RateLimit-Reset", response.reset);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ const schemaBookingBaseBodyParams = Booking.pick({
|
|||||||
|
|
||||||
export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(schemaQueryUserId.partial());
|
export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(schemaQueryUserId.partial());
|
||||||
|
|
||||||
|
export const schemaBookingGetParams = z.object({
|
||||||
|
dateFrom: iso8601.optional(),
|
||||||
|
dateTo: iso8601.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
const schemaBookingEditParams = z
|
const schemaBookingEditParams = z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
|
|||||||
bookingLimits: true,
|
bookingLimits: true,
|
||||||
onlyShowFirstAvailableSlot: true,
|
onlyShowFirstAvailableSlot: true,
|
||||||
durationLimits: true,
|
durationLimits: true,
|
||||||
|
assignAllTeamMembers: true,
|
||||||
})
|
})
|
||||||
.merge(
|
.merge(
|
||||||
z.object({
|
z.object({
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const membershipEditBodySchema = Membership.omit({
|
|||||||
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
||||||
teamId: true,
|
teamId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
id: true,
|
||||||
})
|
})
|
||||||
.partial({
|
.partial({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import { baseApiParams } from "./baseApiParams";
|
||||||
|
|
||||||
|
export const schemaQuerySlug = baseApiParams.extend({
|
||||||
|
slug: z.string().optional(),
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import tzdata from "tzdata";
|
import tzdata from "tzdata";
|
||||||
import * as z from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
|
// @note: This is a custom validation that checks if the timezone is valid and exists in the tzdb library
|
||||||
export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), {
|
export const timeZone = z.string().refine((tz: string) => Object.keys(tzdata.zones).includes(tz), {
|
||||||
|
|||||||
@@ -5,14 +5,23 @@ import { _TeamModel as Team } from "@calcom/prisma/zod";
|
|||||||
export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({
|
export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
|
pendingPayment: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const schemaTeamRequiredParams = z.object({});
|
const schemaTeamRequiredParams = z.object({
|
||||||
|
name: z.string().max(255),
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict();
|
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict();
|
||||||
|
|
||||||
export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial();
|
export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial();
|
||||||
|
|
||||||
|
const schemaOwnerId = z.object({
|
||||||
|
ownerId: z.number().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const schemaTeamCreateBodyParams = schemaTeamBodyParams.merge(schemaOwnerId).strict();
|
||||||
|
|
||||||
export const schemaTeamReadPublic = Team.omit({});
|
export const schemaTeamReadPublic = Team.omit({});
|
||||||
|
|
||||||
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"@sentry/nextjs": "^7.73.0",
|
"@sentry/nextjs": "^7.73.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"next-api-middleware": "^1.0.1",
|
"next-api-middleware": "^1.0.1",
|
||||||
"next-axiom": "^0.17.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-swagger-doc": "^0.3.6",
|
"next-swagger-doc": "^0.3.6",
|
||||||
@@ -41,6 +41,6 @@
|
|||||||
"typescript": "^4.9.4",
|
"typescript": "^4.9.4",
|
||||||
"tzdata": "^1.0.30",
|
"tzdata": "^1.0.30",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +197,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
dateTo,
|
dateTo,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
userId,
|
userId,
|
||||||
|
returnDateOverrides: true,
|
||||||
});
|
});
|
||||||
const team = await prisma.team.findUnique({
|
const team = await prisma.team.findUnique({
|
||||||
where: { id: teamId },
|
where: { id: teamId },
|
||||||
@@ -232,6 +233,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
dateFrom,
|
dateFrom,
|
||||||
dateTo,
|
dateTo,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
|
returnDateOverrides: true,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { NextApiRequest } from "next";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
import { schemaBookingReadPublic } from "~/lib/validations/booking";
|
import { schemaBookingGetParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||||
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
@@ -119,13 +119,13 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
|||||||
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
||||||
*/
|
*/
|
||||||
function buildWhereClause(
|
function buildWhereClause(
|
||||||
userId: number,
|
userId: number | null,
|
||||||
attendeeEmails: string[],
|
attendeeEmails: string[],
|
||||||
userIds: number[] = [],
|
userIds: number[] = [],
|
||||||
userEmails: string[] = []
|
userEmails: string[] = []
|
||||||
) {
|
) {
|
||||||
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||||
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : { userId };
|
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : !!userId ? { userId } : {};
|
||||||
let whereClause = {};
|
let whereClause = {};
|
||||||
if (filterByAttendeeEmails) {
|
if (filterByAttendeeEmails) {
|
||||||
whereClause = {
|
whereClause = {
|
||||||
@@ -162,6 +162,9 @@ function buildWhereClause(
|
|||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, prisma } = req;
|
const { userId, isAdmin, prisma } = req;
|
||||||
|
|
||||||
|
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
|
||||||
|
|
||||||
const args: Prisma.BookingFindManyArgs = {};
|
const args: Prisma.BookingFindManyArgs = {};
|
||||||
args.include = {
|
args.include = {
|
||||||
attendees: true,
|
attendees: true,
|
||||||
@@ -189,7 +192,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
const userEmails = users.map((u) => u.email);
|
const userEmails = users.map((u) => u.email);
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
||||||
} else if (filterByAttendeeEmails) {
|
} else if (filterByAttendeeEmails) {
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
args.where = buildWhereClause(null, attendeeEmails, [], []);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@@ -203,6 +206,20 @@ async function handler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dateFrom) {
|
||||||
|
args.where = {
|
||||||
|
...args.where,
|
||||||
|
startTime: { gte: dateFrom },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (dateTo) {
|
||||||
|
args.where = {
|
||||||
|
...args.where,
|
||||||
|
endTime: { lte: dateTo },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const data = await prisma.booking.findMany(args);
|
const data = await prisma.booking.findMany(args);
|
||||||
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
|
import getBookingDataSchemaForApi from "@calcom/features/bookings/lib/getBookingDataSchemaForApi";
|
||||||
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
@@ -206,7 +207,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
const { userId, isAdmin } = req;
|
const { userId, isAdmin } = req;
|
||||||
if (isAdmin) req.userId = req.body.userId || userId;
|
if (isAdmin) req.userId = req.body.userId || userId;
|
||||||
|
|
||||||
return await handleNewBooking(req);
|
return await handleNewBooking(req, getBookingDataSchemaForApi);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(handler);
|
export default defaultResponder(handler);
|
||||||
@@ -51,7 +51,6 @@ export async function getHandler(req: NextApiRequest) {
|
|||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defaultResponder } from "@calcom/lib/server";
|
|||||||
import type { PrismaClient } from "@calcom/prisma";
|
import type { PrismaClient } from "@calcom/prisma";
|
||||||
|
|
||||||
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
|
||||||
|
import { schemaQuerySlug } from "~/lib/validations/shared/querySlug";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
import getCalLink from "./_utils/getCalLink";
|
import getCalLink from "./_utils/getCalLink";
|
||||||
@@ -22,6 +23,12 @@ import getCalLink from "./_utils/getCalLink";
|
|||||||
* type: string
|
* type: string
|
||||||
* required: true
|
* required: true
|
||||||
* description: Your API Key
|
* description: Your API Key
|
||||||
|
* - in: query
|
||||||
|
* name: slug
|
||||||
|
* schema:
|
||||||
|
* type: string
|
||||||
|
* required: false
|
||||||
|
* description: Slug to filter event types by
|
||||||
* tags:
|
* tags:
|
||||||
* - event-types
|
* - event-types
|
||||||
* externalDocs:
|
* externalDocs:
|
||||||
@@ -35,16 +42,20 @@ import getCalLink from "./_utils/getCalLink";
|
|||||||
* description: No event types were found
|
* description: No event types were found
|
||||||
*/
|
*/
|
||||||
async function getHandler(req: NextApiRequest) {
|
async function getHandler(req: NextApiRequest) {
|
||||||
const { userId, prisma } = req;
|
const { userId, prisma, isAdmin } = req;
|
||||||
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
const userIds = req.query.userId ? extractUserIdsFromQuery(req) : [userId];
|
||||||
|
const { slug } = schemaQuerySlug.parse(req.query);
|
||||||
|
const shouldUseUserId = !isAdmin || !slug || !!req.query.userId;
|
||||||
|
// When user is admin and no query params are provided we should return all event types.
|
||||||
|
// But currently we return only the event types of the user. Not changing this for backwards compatibility.
|
||||||
const data = await prisma.eventType.findMany({
|
const data = await prisma.eventType.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: { in: userIds },
|
userId: shouldUseUserId ? { in: userIds } : undefined,
|
||||||
|
slug: slug, // slug will be undefined if not provided in query
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||||
|
|
||||||
export default function getCalLink(eventType: {
|
export default function getCalLink(eventType: {
|
||||||
team?: { slug: string | null } | null;
|
team?: { slug: string | null } | null;
|
||||||
@@ -6,7 +6,7 @@ export default function getCalLink(eventType: {
|
|||||||
users?: { username: string | null }[];
|
users?: { username: string | null }[];
|
||||||
slug: string;
|
slug: string;
|
||||||
}) {
|
}) {
|
||||||
return `${CAL_URL}/${
|
return `${WEBSITE_URL}/${
|
||||||
eventType?.team
|
eventType?.team
|
||||||
? `team/${eventType?.team?.slug}`
|
? `team/${eventType?.team?.slug}`
|
||||||
: eventType?.owner
|
: eventType?.owner
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
import type { TInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||||
|
import { ZInviteMemberInputSchema } from "@calcom/trpc/server/routers/viewer/teams/inviteMember/inviteMember.schema";
|
||||||
|
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||||
|
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||||
|
|
||||||
|
async function postHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
|
const data = ZInviteMemberInputSchema.parse(req.body);
|
||||||
|
await checkPermissions(req, data);
|
||||||
|
|
||||||
|
async function sessionGetter() {
|
||||||
|
return {
|
||||||
|
user: {
|
||||||
|
id: req.userId,
|
||||||
|
username: "",
|
||||||
|
profile: {
|
||||||
|
id: null,
|
||||||
|
organizationId: null,
|
||||||
|
organization: null,
|
||||||
|
username: "",
|
||||||
|
upId: "",
|
||||||
|
} satisfies UserProfile,
|
||||||
|
},
|
||||||
|
hasValidLicense: true,
|
||||||
|
expires: "",
|
||||||
|
upId: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = await createContext({ req, res }, sessionGetter);
|
||||||
|
try {
|
||||||
|
const caller = viewerTeamsRouter.createCaller(ctx);
|
||||||
|
await caller.inviteMember({
|
||||||
|
role: data.role,
|
||||||
|
language: data.language,
|
||||||
|
teamId: data.teamId,
|
||||||
|
usernameOrEmail: data.usernameOrEmail,
|
||||||
|
isOrg: data.isOrg,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: `${data.usernameOrEmail} has been invited.` };
|
||||||
|
} catch (cause) {
|
||||||
|
if (cause instanceof TRPCError) {
|
||||||
|
const statusCode = getHTTPStatusCodeFromError(cause);
|
||||||
|
throw new HttpError({ statusCode, message: cause.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkPermissions(req: NextApiRequest, body: TInviteMemberInputSchema) {
|
||||||
|
const { userId, isAdmin, prisma } = req;
|
||||||
|
if (isAdmin) return;
|
||||||
|
// To prevent auto-accepted invites, limit it to ADMIN users
|
||||||
|
if (!isAdmin && "accepted" in body)
|
||||||
|
throw new HttpError({ statusCode: 403, message: "ADMIN needed for `accepted`" });
|
||||||
|
// Only team OWNERS and ADMINS can add other members
|
||||||
|
const membership = await prisma.membership.findFirst({
|
||||||
|
where: { userId, teamId: body.teamId, role: { in: ["ADMIN", "OWNER"] } },
|
||||||
|
});
|
||||||
|
if (!membership) throw new HttpError({ statusCode: 403, message: "You can't add members to this team" });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defaultResponder(postHandler);
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { defaultHandler } from "@calcom/lib/server";
|
||||||
|
|
||||||
|
import { withMiddleware } from "~/lib/helpers/withMiddleware";
|
||||||
|
|
||||||
|
export default withMiddleware()(
|
||||||
|
defaultHandler({
|
||||||
|
POST: import("./_post"),
|
||||||
|
})
|
||||||
|
);
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
||||||
|
|
||||||
|
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 dayjs from "@calcom/dayjs";
|
||||||
|
|
||||||
|
import handler from "./_get";
|
||||||
|
|
||||||
|
type CustomNextApiRequest = NextApiRequest & Request;
|
||||||
|
type CustomNextApiResponse = NextApiResponse & Response;
|
||||||
|
|
||||||
|
function buildMockData() {
|
||||||
|
prismock.user.create({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
username: "test",
|
||||||
|
name: "Test User",
|
||||||
|
email: "test@example.com",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
prismock.eventType.create({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
slug: "test",
|
||||||
|
length: 30,
|
||||||
|
title: "Test Event Type",
|
||||||
|
userId: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("GET /api/slots", () => {
|
||||||
|
describe("Errors", () => {
|
||||||
|
test("Missing required data", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
await handler(req, res);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"message": "invalid_type in 'startTime': Required; invalid_type in 'endTime': Required",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Success", () => {
|
||||||
|
describe("Regular event-type", () => {
|
||||||
|
test("Returns and event type available slots", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
query: {
|
||||||
|
eventTypeId: 1,
|
||||||
|
startTime: dayjs().format(),
|
||||||
|
endTime: dayjs().add(1, "day").format(),
|
||||||
|
usernameList: "test",
|
||||||
|
},
|
||||||
|
prisma: prismock,
|
||||||
|
});
|
||||||
|
buildMockData();
|
||||||
|
await handler(req, res);
|
||||||
|
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"slots": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
test("Returns and event type available slots with passed timeZone", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
query: {
|
||||||
|
eventTypeId: 1,
|
||||||
|
startTime: dayjs().format(),
|
||||||
|
endTime: dayjs().add(1, "day").format(),
|
||||||
|
usernameList: "test",
|
||||||
|
timeZone: "UTC",
|
||||||
|
},
|
||||||
|
prisma: prismock,
|
||||||
|
});
|
||||||
|
buildMockData();
|
||||||
|
await handler(req, res);
|
||||||
|
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"slots": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,7 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const { usernameList, ...rest } = req.query;
|
const { usernameList, ...rest } = req.query;
|
||||||
let slugs = usernameList;
|
let slugs = usernameList;
|
||||||
if (!Array.isArray(usernameList)) {
|
if (!Array.isArray(usernameList)) {
|
||||||
slugs = usernameList ? [usernameList] : [];
|
slugs = usernameList ? [usernameList] : undefined;
|
||||||
}
|
}
|
||||||
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
|
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
|
||||||
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
|
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
|
||||||
@@ -38,9 +38,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
: availableSlots;
|
: availableSlots.slots;
|
||||||
|
|
||||||
return slotsInProvidedTimeZone;
|
return { slots: slotsInProvidedTimeZone };
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
if (cause instanceof TRPCError) {
|
if (cause instanceof TRPCError) {
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ async function getHandler(req: NextApiRequest) {
|
|||||||
include: {
|
include: {
|
||||||
customInputs: true,
|
customInputs: true,
|
||||||
team: { select: { slug: true } },
|
team: { select: { slug: true } },
|
||||||
users: true,
|
|
||||||
hosts: { select: { userId: true, isFixed: true } },
|
hosts: { select: { userId: true, isFixed: true } },
|
||||||
owner: { select: { username: true, id: true } },
|
owner: { select: { username: true, id: true } },
|
||||||
children: { select: { id: true, userId: true } },
|
children: { select: { id: true, userId: true } },
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
|||||||
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
import { MembershipRole, UserPermissionRole } from "@calcom/prisma/enums";
|
||||||
import { createContext } from "@calcom/trpc/server/createContext";
|
import { createContext } from "@calcom/trpc/server/createContext";
|
||||||
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
import { viewerTeamsRouter } from "@calcom/trpc/server/routers/viewer/teams/_router";
|
||||||
|
import type { UserProfile } from "@calcom/types/UserProfile";
|
||||||
|
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
import { getHTTPStatusCodeFromError } from "@trpc/server/http";
|
||||||
@@ -21,9 +22,17 @@ const patchHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
id: req.userId,
|
id: req.userId,
|
||||||
username: "" /* Not used in this context */,
|
username: "" /* Not used in this context */,
|
||||||
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
|
role: req.isAdmin ? UserPermissionRole.ADMIN : UserPermissionRole.USER,
|
||||||
|
profile: {
|
||||||
|
id: null,
|
||||||
|
organizationId: null,
|
||||||
|
organization: null,
|
||||||
|
username: "",
|
||||||
|
upId: "",
|
||||||
|
} satisfies UserProfile,
|
||||||
},
|
},
|
||||||
hasValidLicense: true /* To comply with TS signature */,
|
hasValidLicense: true /* To comply with TS signature */,
|
||||||
expires: "" /* Not used in this context */,
|
expires: "" /* Not used in this context */,
|
||||||
|
upId: "",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/** @see https://trpc.io/docs/server-side-calls */
|
/** @see https://trpc.io/docs/server-side-calls */
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
|
||||||
|
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||||
|
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { schemaMembershipPublic } from "~/lib/validations/membership";
|
import { schemaMembershipPublic } from "~/lib/validations/membership";
|
||||||
import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/team";
|
import { schemaTeamCreateBodyParams, schemaTeamReadPublic } from "~/lib/validations/team";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@@ -31,6 +33,12 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* required:
|
* required:
|
||||||
* - name
|
* - name
|
||||||
* - slug
|
* - slug
|
||||||
|
* - hideBookATeamMember
|
||||||
|
* - brandColor
|
||||||
|
* - darkBrandColor
|
||||||
|
* - timeZone
|
||||||
|
* - weekStart
|
||||||
|
* - isPrivate
|
||||||
* properties:
|
* properties:
|
||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
@@ -38,6 +46,27 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* slug:
|
* slug:
|
||||||
* type: string
|
* type: string
|
||||||
* description: A unique slug that works as path for the team public page
|
* description: A unique slug that works as path for the team public page
|
||||||
|
* hideBookATeamMember:
|
||||||
|
* type: boolean
|
||||||
|
* description: Flag to hide or show the book a team member option
|
||||||
|
* brandColor:
|
||||||
|
* type: string
|
||||||
|
* description: Primary brand color for the team
|
||||||
|
* darkBrandColor:
|
||||||
|
* type: string
|
||||||
|
* description: Dark variant of the primary brand color for the team
|
||||||
|
* timeZone:
|
||||||
|
* type: string
|
||||||
|
* description: Time zone of the team
|
||||||
|
* weekStart:
|
||||||
|
* type: string
|
||||||
|
* description: Starting day of the week for the team
|
||||||
|
* isPrivate:
|
||||||
|
* type: boolean
|
||||||
|
* description: Flag indicating if the team is private
|
||||||
|
* ownerId:
|
||||||
|
* type: number
|
||||||
|
* description: ID of the team owner - only admins can set this.
|
||||||
* tags:
|
* tags:
|
||||||
* - teams
|
* - teams
|
||||||
* responses:
|
* responses:
|
||||||
@@ -49,23 +78,23 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { prisma, body, userId } = req;
|
const { prisma, body, userId, isAdmin } = req;
|
||||||
const data = schemaTeamBodyParams.parse(body);
|
const { ownerId, ...data } = schemaTeamCreateBodyParams.parse(body);
|
||||||
|
|
||||||
|
await checkPermissions(req);
|
||||||
|
|
||||||
|
const effectiveUserId = isAdmin && ownerId ? ownerId : userId;
|
||||||
|
|
||||||
if (data.slug) {
|
if (data.slug) {
|
||||||
const alreadyExist = await prisma.team.findFirst({
|
const alreadyExist = await prisma.team.findFirst({
|
||||||
where: {
|
where: {
|
||||||
slug: data.slug,
|
slug: {
|
||||||
|
mode: "insensitive",
|
||||||
|
equals: data.slug,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
|
if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
|
||||||
if (IS_TEAM_BILLING_ENABLED) {
|
|
||||||
// Setting slug in metadata, so it can be published later
|
|
||||||
data.metadata = {
|
|
||||||
requestedSlug: data.slug,
|
|
||||||
};
|
|
||||||
delete data.slug;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if parentId is related to this user
|
// Check if parentId is related to this user
|
||||||
@@ -87,24 +116,102 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
...data,
|
...data,
|
||||||
metadata: data.metadata === null ? {} : data.metadata || undefined,
|
metadata: data.metadata === null ? {} : data.metadata || undefined,
|
||||||
};
|
};
|
||||||
const team = await prisma.team.create({
|
|
||||||
|
if (!IS_TEAM_BILLING_ENABLED) {
|
||||||
|
const team = await prisma.team.create({
|
||||||
|
data: {
|
||||||
|
...cloneData,
|
||||||
|
members: {
|
||||||
|
create: { userId: effectiveUserId, role: MembershipRole.OWNER, accepted: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: { members: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
req.statusCode = 201;
|
||||||
|
|
||||||
|
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.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingPaymentTeam = await prisma.team.create({
|
||||||
data: {
|
data: {
|
||||||
...cloneData,
|
...cloneData,
|
||||||
createdAt: new Date(),
|
pendingPayment: true,
|
||||||
members: {
|
|
||||||
// We're also creating the relation membership of team ownership in this call.
|
|
||||||
create: { userId, role: MembershipRole.OWNER, accepted: true },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
include: { members: true },
|
|
||||||
});
|
});
|
||||||
req.statusCode = 201;
|
|
||||||
// We are also returning the new ownership relation as owner besides team.
|
const checkoutSession = await generateTeamCheckoutSession({
|
||||||
|
pendingPaymentTeamId: pendingPaymentTeam.id,
|
||||||
|
ownerId: effectiveUserId,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
team: schemaTeamReadPublic.parse(team),
|
message:
|
||||||
owner: schemaMembershipPublic.parse(team.members[0]),
|
"Your team will be created once we receive your payment. Please complete the payment using the payment link.",
|
||||||
message: "Team created successfully, we also made you the owner of this team",
|
paymentLink: checkoutSession.url,
|
||||||
|
pendingTeam: {
|
||||||
|
...schemaTeamReadPublic.parse(pendingPaymentTeam),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
|
const { isAdmin } = req;
|
||||||
|
const body = schemaTeamCreateBodyParams.parse(req.body);
|
||||||
|
|
||||||
|
/* Non-admin users can only create teams for themselves */
|
||||||
|
if (!isAdmin && body.ownerId)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 401,
|
||||||
|
message: "ADMIN required for `ownerId`",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateTeamCheckoutSession = async ({
|
||||||
|
pendingPaymentTeamId,
|
||||||
|
ownerId,
|
||||||
|
}: {
|
||||||
|
pendingPaymentTeamId: number;
|
||||||
|
ownerId: number;
|
||||||
|
}) => {
|
||||||
|
const customer = await getStripeCustomerIdFromUserId(ownerId);
|
||||||
|
const session = await stripe.checkout.sessions.create({
|
||||||
|
customer,
|
||||||
|
mode: "subscription",
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
success_url: `${WEBAPP_URL}/api/teams/api/create?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
|
cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
/** We only need to set the base price and we can upsell it directly on Stripe's checkout */
|
||||||
|
price: process.env.STRIPE_TEAM_MONTHLY_PRICE_ID,
|
||||||
|
/**Initially it will be just the team owner */
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customer_update: {
|
||||||
|
address: "auto",
|
||||||
|
},
|
||||||
|
automatic_tax: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
pendingPaymentTeamId,
|
||||||
|
ownerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session.url)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 500,
|
||||||
|
message: "Failed generating a checkout session URL.",
|
||||||
|
});
|
||||||
|
|
||||||
|
return session;
|
||||||
|
};
|
||||||
|
|
||||||
export default defaultResponder(postHandler);
|
export default defaultResponder(postHandler);
|
||||||
@@ -39,7 +39,7 @@ export async function deleteHandler(req: NextApiRequest) {
|
|||||||
const { prisma, query } = req;
|
const { prisma, query } = req;
|
||||||
const { id } = schemaQueryIdAsString.parse(query);
|
const { id } = schemaQueryIdAsString.parse(query);
|
||||||
await prisma.webhook.delete({ where: { id } });
|
await prisma.webhook.delete({ where: { id } });
|
||||||
return { message: `Schedule with id: ${id} deleted successfully` };
|
return { message: `Webhook with id: ${id} deleted successfully` };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defaultResponder(deleteHandler);
|
export default defaultResponder(deleteHandler);
|
||||||
@@ -92,7 +92,7 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.data.eventTriggers) {
|
if (eventTriggers) {
|
||||||
const eventTriggersSet = new Set(eventTriggers);
|
const eventTriggersSet = new Set(eventTriggers);
|
||||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.data.eventTriggers) {
|
if (eventTriggers) {
|
||||||
const eventTriggersSet = new Set(eventTriggers);
|
const eventTriggersSet = new Set(eventTriggers);
|
||||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { init as SentryInit } from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
SentryInit({
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
});
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as Sentry from "@sentry/nextjs";
|
import { init as SentryInit } from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
SentryInit({
|
||||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
tracesSampleRate: 1.0,
|
tracesSampleRate: 1.0,
|
||||||
});
|
});
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"@storybook/blocks": "^7.6.3",
|
"@storybook/blocks": "^7.6.3",
|
||||||
"@storybook/nextjs": "^7.6.3",
|
"@storybook/nextjs": "^7.6.3",
|
||||||
"@storybook/preview-api": "^7.6.3",
|
"@storybook/preview-api": "^7.6.3",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"storybook-addon-rtl-direction": "^0.0.19"
|
"storybook-addon-rtl-direction": "^0.0.19"
|
||||||
|
|||||||
File renamed without changes.
@@ -14,7 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"highlight.js": "^11.6.0",
|
"highlight.js": "^11.6.0",
|
||||||
"isarray": "2.0.5",
|
"isarray": "2.0.5",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"openapi-snippet": "^0.13.0",
|
"openapi-snippet": "^0.13.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@types/node": "16.9.1",
|
"@types/node": "16.9.1",
|
||||||
"@types/react": "18.0.26",
|
"@types/react": "18.0.26",
|
||||||
"@types/react-dom": "^18.0.9",
|
"@types/react-dom": "^18.0.9",
|
||||||
|
"@types/swagger-ui-react": "^4.18.3",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import "highlight.js/styles/default.css";
|
import "highlight.js/styles/default.css";
|
||||||
|
import { type AppProps } from "next/app";
|
||||||
import "swagger-ui-react/swagger-ui.css";
|
import "swagger-ui-react/swagger-ui.css";
|
||||||
|
|
||||||
import "../styles/globals.css";
|
import "../styles/globals.css";
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return <Component {...pageProps} />;
|
return <Component {...pageProps} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import type { SwaggerUI } from "swagger-ui-react";
|
|
||||||
|
|
||||||
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
|
import { SnippedGenerator, requestSnippets } from "@lib/snippets";
|
||||||
|
|
||||||
const SwaggerUIDynamic: SwaggerUI & { url: string } = dynamic(() => import("swagger-ui-react"), {
|
const SwaggerUIDynamic = dynamic(() => import("swagger-ui-react"), {
|
||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -17,8 +16,6 @@ export default function APIDocs() {
|
|||||||
requestSnippets={requestSnippets}
|
requestSnippets={requestSnippets}
|
||||||
plugins={[SnippedGenerator]}
|
plugins={[SnippedGenerator]}
|
||||||
tryItOutEnabled={true}
|
tryItOutEnabled={true}
|
||||||
syntaxHighlight={true}
|
|
||||||
enableCORS={false} // Doesn't seem to work either
|
|
||||||
docExpansion="list"
|
docExpansion="list"
|
||||||
filter={true}
|
filter={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { NextMiddleware, NextRequest } from "next/server";
|
|||||||
import { NextResponse, URLPattern } from "next/server";
|
import { NextResponse, URLPattern } from "next/server";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
|
import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
const ROUTES: [URLPattern, boolean][] = [
|
const ROUTES: [URLPattern, boolean][] = [
|
||||||
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
|
["/event-types", process.env.APP_ROUTER_EVENT_TYPES_ENABLED === "1"] as const,
|
||||||
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
|
["/settings/admin/:path*", process.env.APP_ROUTER_SETTINGS_ADMIN_ENABLED === "1"] as const,
|
||||||
@@ -25,9 +27,6 @@ const ROUTES: [URLPattern, boolean][] = [
|
|||||||
enabled,
|
enabled,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const FUTURE_ROUTES_OVERRIDE_COOKIE_NAME = "x-calcom-future-routes-override";
|
|
||||||
const FUTURE_ROUTES_ENABLED_COOKIE_NAME = "x-calcom-future-routes-enabled";
|
|
||||||
|
|
||||||
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
|
const bucketSchema = z.union([z.literal("legacy"), z.literal("future")]);
|
||||||
|
|
||||||
export const abTestMiddlewareFactory =
|
export const abTestMiddlewareFactory =
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { GetStaticProps, GetStaticPropsContext } from "next";
|
||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export const withAppDirSsg =
|
||||||
|
<T extends Record<string, any>>(getStaticProps: GetStaticProps<T>) =>
|
||||||
|
async (context: GetStaticPropsContext) => {
|
||||||
|
const ssgResponse = await getStaticProps(context);
|
||||||
|
|
||||||
|
if ("redirect" in ssgResponse) {
|
||||||
|
redirect(ssgResponse.redirect.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("notFound" in ssgResponse) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = await Promise.resolve(ssgResponse.props);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...ssgResponse.props,
|
||||||
|
// includes dehydratedState required for future page trpcPropvider
|
||||||
|
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
|
||||||
export const withAppDir =
|
export const withAppDirSsr =
|
||||||
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
||||||
async (context: GetServerSidePropsContext): Promise<T> => {
|
async (context: GetServerSidePropsContext) => {
|
||||||
const ssrResponse = await getServerSideProps(context);
|
const ssrResponse = await getServerSideProps(context);
|
||||||
|
|
||||||
if ("redirect" in ssrResponse) {
|
if ("redirect" in ssrResponse) {
|
||||||
@@ -13,9 +13,11 @@ export const withAppDir =
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = await Promise.resolve(ssrResponse.props);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...ssrResponse.props,
|
...props,
|
||||||
// includes dehydratedState required for future page trpcPropvider
|
// includes dehydratedState required for future page trpcPropvider
|
||||||
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
|
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type DehydratedState, Hydrate } from "@tanstack/react-query";
|
import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import type { DataTransformer } from "@trpc/server";
|
type DataTransformer = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
serialize: (data: any) => any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
deserialize: (data: any) => any;
|
||||||
|
};
|
||||||
|
|
||||||
export function createHydrateClient(opts: { transformer?: DataTransformer }) {
|
export function createHydrateClient(opts: { transformer: DataTransformer }) {
|
||||||
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
|
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
|
||||||
const { state, children } = props;
|
const { state, children } = props;
|
||||||
|
|
||||||
@@ -16,6 +21,6 @@ export function createHydrateClient(opts: { transformer?: DataTransformer }) {
|
|||||||
return state;
|
return state;
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
return <Hydrate state={transformedState}>{children}</Hydrate>;
|
return <HydrationBoundary state={transformedState}>{children}</HydrationBoundary>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,7 @@ import { trpc } from "app/_trpc/client";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink";
|
import { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client";
|
||||||
import { httpLink } from "@calcom/trpc/client/links/httpLink";
|
|
||||||
import { loggerLink } from "@calcom/trpc/client/links/loggerLink";
|
|
||||||
import { splitLink } from "@calcom/trpc/client/links/splitLink";
|
|
||||||
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
||||||
|
|
||||||
export type Endpoint = (typeof ENDPOINTS)[number];
|
export type Endpoint = (typeof ENDPOINTS)[number];
|
||||||
|
|||||||
+22
-1
@@ -1,12 +1,33 @@
|
|||||||
import { type TFunction } from "i18next";
|
import { type TFunction } from "i18next";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|
||||||
import { constructGenericImage } from "@calcom/lib/OgImages";
|
import { constructGenericImage } from "@calcom/lib/OgImages";
|
||||||
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
|
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG } from "@calcom/lib/constants";
|
||||||
import { getFixedT } from "@calcom/lib/server/getFixedT";
|
//@ts-expect-error no type definitions
|
||||||
|
import config from "@calcom/web/next-i18next.config";
|
||||||
|
|
||||||
import { preparePageMetadata } from "@lib/metadata";
|
import { preparePageMetadata } from "@lib/metadata";
|
||||||
|
|
||||||
|
const create = async (locale: string, ns: string) => {
|
||||||
|
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
|
||||||
|
|
||||||
|
const _i18n = i18next.createInstance();
|
||||||
|
_i18n.init({
|
||||||
|
lng: locale,
|
||||||
|
resources: _nextI18Next?.initialI18nStore,
|
||||||
|
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
|
||||||
|
});
|
||||||
|
return _i18n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFixedT = async (locale: string, ns: string) => {
|
||||||
|
const i18n = await create(locale, ns);
|
||||||
|
|
||||||
|
return i18n.getFixedT(locale, ns);
|
||||||
|
};
|
||||||
|
|
||||||
export const _generateMetadata = async (
|
export const _generateMetadata = async (
|
||||||
getTitle: (t: TFunction<string, undefined>) => string,
|
getTitle: (t: TFunction<string, undefined>) => string,
|
||||||
getDescription: (t: TFunction<string, undefined>) => string
|
getDescription: (t: TFunction<string, undefined>) => string
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-type-public-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { headers, cookies } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-type-public-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/users/views/users-type-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
|
||||||
|
const { eventData, booking, user, slug } = props;
|
||||||
|
const rescheduleUid = booking?.uid;
|
||||||
|
const { trpc } = await import("@calcom/trpc");
|
||||||
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: eventData.entity.orgSlug ?? null },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const profileName = event?.profile?.name ?? "";
|
||||||
|
const title = event?.title ?? "";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${rescheduleUid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||||
|
(t) => `${rescheduleUid ? t("reschedule") : ""} ${title}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getData,
|
||||||
|
Page: LegacyPage,
|
||||||
|
getLayout: null,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-public-view";
|
||||||
|
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { headers, cookies } from "next/headers";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import LegacyPage from "~/users/views/users-public-view";
|
||||||
|
import { getServerSideProps, type UserPageProps } from "~/users/views/users-public-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
|
||||||
|
const { profile, markdownStrippedBio } = props;
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => profile.name,
|
||||||
|
() => markdownStrippedBio
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<UserPageProps>(getServerSideProps);
|
||||||
|
export default WithLayout({ getLayout, getData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import LegacyPage, { getLayout } from "@pages/apps/[slug]/[...pages]";
|
||||||
|
import type { PageProps, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||||
|
import RoutingFormsRoutingConfig, {
|
||||||
|
serverSidePropsConfig,
|
||||||
|
} from "@calcom/app-store/routing-forms/pages/app-routing.config";
|
||||||
|
import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
|
||||||
|
|
||||||
|
import type { AppProps } from "@lib/app-providers";
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapperAppDir";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
type AppPageType = {
|
||||||
|
getServerSideProps: AppGetServerSideProps;
|
||||||
|
// A component than can accept any properties
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
default: ((props: any) => JSX.Element) &
|
||||||
|
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Found = {
|
||||||
|
notFound: false;
|
||||||
|
Component: AppPageType["default"];
|
||||||
|
getServerSideProps: AppPageType["getServerSideProps"];
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppsRouting = {
|
||||||
|
"routing-forms": RoutingFormsRoutingConfig,
|
||||||
|
typeform: TypeformRoutingConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
slug: z.string(),
|
||||||
|
pages: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const p = paramsSchema.safeParse(params);
|
||||||
|
|
||||||
|
if (!p.success) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainPage = p.data.pages[0];
|
||||||
|
|
||||||
|
if (mainPage === "forms") {
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => `Forms | ${APP_NAME}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyContext = buildLegacyCtx(
|
||||||
|
headers(),
|
||||||
|
cookies(),
|
||||||
|
params,
|
||||||
|
searchParams
|
||||||
|
) as unknown as GetServerSidePropsContext;
|
||||||
|
const { form } = await getPageProps(legacyContext);
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => `${form.name} | ${APP_NAME}`,
|
||||||
|
() => form.description
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRoute(appName: string, pages: string[]) {
|
||||||
|
const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as Record<string, AppPageType>;
|
||||||
|
|
||||||
|
if (!routingConfig) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainPage = pages[0];
|
||||||
|
const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as AppPageType);
|
||||||
|
|
||||||
|
const getServerSidePropsHandler = serverSidePropsConfig[mainPage];
|
||||||
|
|
||||||
|
if (!appPage) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notFound: false,
|
||||||
|
Component: appPage.default,
|
||||||
|
...appPage,
|
||||||
|
getServerSideProps: getServerSidePropsHandler,
|
||||||
|
} as Found;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPageProps = async ({ params, query, req }: GetServerSidePropsContext) => {
|
||||||
|
const p = paramsSchema.safeParse(params);
|
||||||
|
|
||||||
|
if (!p.success) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slug: appName, pages } = p.data;
|
||||||
|
|
||||||
|
const route = getRoute(appName, pages);
|
||||||
|
|
||||||
|
if (route.notFound) {
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.getServerSideProps) {
|
||||||
|
// TODO: Document somewhere that right now it is just a convention that filename should have appPages in it's name.
|
||||||
|
// appPages is actually hardcoded here and no matter the fileName the same variable would be used.
|
||||||
|
// We can write some validation logic later on that ensures that [...appPages].tsx file exists
|
||||||
|
params!.appPages = pages.slice(1);
|
||||||
|
|
||||||
|
const ctx = { req, params, query };
|
||||||
|
|
||||||
|
const session = await getServerSession({ req });
|
||||||
|
const user = session?.user;
|
||||||
|
const app = await getAppWithMetadata({ slug: appName });
|
||||||
|
|
||||||
|
if (!app) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await route.getServerSideProps(
|
||||||
|
{
|
||||||
|
...ctx,
|
||||||
|
params: {
|
||||||
|
...ctx.params,
|
||||||
|
appPages: pages.slice(1),
|
||||||
|
},
|
||||||
|
} as GetServerSidePropsContext<{
|
||||||
|
slug: string;
|
||||||
|
pages: string[];
|
||||||
|
appPages: string[];
|
||||||
|
}>,
|
||||||
|
prisma,
|
||||||
|
user,
|
||||||
|
ssrInit
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
|
||||||
|
if (result.notFound) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.redirect) {
|
||||||
|
redirect(result.redirect.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
appName,
|
||||||
|
appUrl: app.simplePath || `/apps/${appName}`,
|
||||||
|
...result.props,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
appName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Page({ params, searchParams }: PageProps) {
|
||||||
|
const h = headers();
|
||||||
|
const nonce = h.get("x-nonce") ?? undefined;
|
||||||
|
|
||||||
|
const legacyContext = buildLegacyCtx(
|
||||||
|
h,
|
||||||
|
cookies(),
|
||||||
|
params,
|
||||||
|
searchParams
|
||||||
|
) as unknown as GetServerSidePropsContext;
|
||||||
|
const props = await getPageProps(legacyContext);
|
||||||
|
return (
|
||||||
|
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||||
|
<LegacyPage {...props} />
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null })<"L">;
|
|
||||||
@@ -1,40 +1,33 @@
|
|||||||
import AppPage from "@pages/apps/[slug]/index";
|
import Page from "@pages/apps/[slug]/index";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import fs from "fs";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import matter from "gray-matter";
|
import type { InferGetStaticPropsType } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { cookies, headers } from "next/headers";
|
||||||
import path from "path";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
|
||||||
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
const sourceSchema = z.object({
|
import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
|
||||||
content: z.string(),
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
data: z.object({
|
|
||||||
description: z.string().optional(),
|
|
||||||
items: z
|
|
||||||
.array(
|
|
||||||
z.union([
|
|
||||||
z.string(),
|
|
||||||
z.object({
|
|
||||||
iframe: z.object({ src: z.string() }),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
const { data } = await getPageProps({ params });
|
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const res = await getData(legacyContext);
|
||||||
|
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => `${data.name} | ${APP_NAME}`,
|
() => `${res?.data.name} | ${APP_NAME}`,
|
||||||
() => data.description
|
() => res?.data.description ?? ""
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,74 +46,6 @@ export const generateStaticParams = async () => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
export default WithLayout({ getLayout: null, Page, getData });
|
||||||
if (typeof params?.slug !== "string") {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const appMeta = await getAppWithMetadata({
|
|
||||||
slug: params?.slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appFromDb = await prisma.app.findUnique({
|
|
||||||
where: { slug: params.slug.toLowerCase() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAppAvailableInFileSystem = appMeta;
|
|
||||||
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
|
|
||||||
|
|
||||||
if (!IS_PRODUCTION && isAppDisabled) {
|
|
||||||
return {
|
|
||||||
isAppDisabled: true as const,
|
|
||||||
data: {
|
|
||||||
...appMeta,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appFromDb || !appMeta || isAppDisabled) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTemplate = appMeta.isTemplate;
|
|
||||||
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
|
|
||||||
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
|
|
||||||
const postFilePath = path.join(README_PATH);
|
|
||||||
let source = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
source = fs.readFileSync(postFilePath).toString();
|
|
||||||
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
|
|
||||||
} catch (error) {
|
|
||||||
/* If the app doesn't have a README we fallback to the package description */
|
|
||||||
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
|
||||||
source = appMeta.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = matter(source);
|
|
||||||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
|
||||||
if (data.items) {
|
|
||||||
data.items = data.items.map((item) => {
|
|
||||||
if (typeof item === "string") {
|
|
||||||
return getAppAssetFullPath(item, {
|
|
||||||
dirName: appMeta.dirName,
|
|
||||||
isTemplate: appMeta.isTemplate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isAppDisabled: false as const,
|
|
||||||
source: { content, data },
|
|
||||||
data: appMeta,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
const pageProps = await getPageProps({ params });
|
|
||||||
|
|
||||||
return <AppPage {...pageProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import SetupPage from "@pages/apps/[slug]/setup";
|
import Page from "@pages/apps/[slug]/setup";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { cookies, headers } from "next/headers";
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
import { notFound, redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
|
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
@@ -14,23 +14,8 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
type T = InferGetServerSidePropsType<typeof getServerSideProps>;
|
||||||
const req = { headers: headers(), cookies: cookies() };
|
|
||||||
|
|
||||||
const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);
|
const getData = withAppDirSsr<T>(getServerSideProps);
|
||||||
|
|
||||||
if (!result || "notFound" in result) {
|
export default WithLayout({ getLayout: null, Page, getData });
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("redirect" in result) {
|
|
||||||
redirect(result.redirect.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.props;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
const pageProps = await getPageProps({ params });
|
|
||||||
return <SetupPage {...pageProps} />;
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import CategoryPage from "@pages/apps/categories/[category]";
|
import CategoryPage, { type PageProps } from "@pages/apps/categories/[category]";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
import { AppCategories } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => `${APP_NAME} | ${APP_NAME}`,
|
() => `${APP_NAME} | ${APP_NAME}`,
|
||||||
@@ -34,38 +34,7 @@ export const generateStaticParams = async () => {
|
|||||||
return paths.map((category) => ({ category }));
|
return paths.map((category) => ({ category }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const querySchema = z.object({
|
const getData = withAppDirSsg<PageProps>(getStaticProps);
|
||||||
category: z.nativeEnum(AppCategories),
|
|
||||||
});
|
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
export default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">;
|
||||||
const p = querySchema.safeParse(params);
|
|
||||||
|
|
||||||
if (!p.success) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const appQuery = await prisma.app.findMany({
|
|
||||||
where: {
|
|
||||||
categories: {
|
|
||||||
has: p.data.category,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
slug: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbAppsSlugs = appQuery.map((category) => category.slug);
|
|
||||||
|
|
||||||
const appStore = await getAppRegistry();
|
|
||||||
|
|
||||||
const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug));
|
|
||||||
return {
|
|
||||||
apps,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error getData arg
|
|
||||||
export default WithLayout({ getData: getPageProps, Page: CategoryPage })<"P">;
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import LegacyPage from "@pages/apps/categories/index";
|
import Page from "@pages/apps/categories/index";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/apps/categories/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -16,29 +14,4 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), Page, getLayout: null })<"P">;
|
||||||
const ssr = await ssrInit(ctx);
|
|
||||||
|
|
||||||
const session = await getServerSession({ req: ctx.req });
|
|
||||||
|
|
||||||
let appStore;
|
|
||||||
if (session?.user?.id) {
|
|
||||||
appStore = await getAppRegistryWithCredentials(session.user.id);
|
|
||||||
} else {
|
|
||||||
appStore = await getAppRegistry();
|
|
||||||
}
|
|
||||||
|
|
||||||
const categories = appStore.reduce((c, app) => {
|
|
||||||
for (const category of app.categories) {
|
|
||||||
c[category] = c[category] ? c[category] + 1 : 1;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}, {} as Record<string, number>);
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null })<"L">;
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import LegacyPage from "@pages/apps/installed/[category]";
|
import Page from "@pages/apps/installed/[category]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { notFound } from "next/navigation";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
const querySchema = z.object({
|
import { getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps";
|
||||||
category: z.nativeEnum(AppCategories),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -17,20 +14,6 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const p = querySchema.safeParse(params);
|
|
||||||
|
|
||||||
if (!p.success) {
|
export default WithLayout({ getLayout: null, getData, Page });
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
category: p.data.category,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
await getPageProps({ params });
|
|
||||||
|
|
||||||
return <LegacyPage />;
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
import AppsPage from "@pages/apps";
|
import AppsPage from "@pages/apps";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
|
||||||
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import type { AppCategories } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/apps/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -20,44 +15,4 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
export default WithLayout({ getLayout, getData: withAppDirSsr(getServerSideProps), Page: AppsPage });
|
||||||
const ssr = await ssrInit(ctx);
|
|
||||||
|
|
||||||
const session = await getServerSession({ req: ctx.req });
|
|
||||||
|
|
||||||
let appStore, userAdminTeams: UserAdminTeams;
|
|
||||||
if (session?.user?.id) {
|
|
||||||
userAdminTeams = await getUserAdminTeams({ userId: session.user.id, getUserInfo: true });
|
|
||||||
appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeams);
|
|
||||||
} else {
|
|
||||||
appStore = await getAppRegistry();
|
|
||||||
userAdminTeams = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const categoryQuery = appStore.map(({ categories }) => ({
|
|
||||||
categories: categories || [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const categories = categoryQuery.reduce((c, app) => {
|
|
||||||
for (const category of app.categories) {
|
|
||||||
c[category] = c[category] ? c[category] + 1 : 1;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}, {} as Record<string, number>);
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories: Object.entries(categories)
|
|
||||||
.map(([name, count]): { name: AppCategories; count: number } => ({
|
|
||||||
name: name as AppCategories,
|
|
||||||
count,
|
|
||||||
}))
|
|
||||||
.sort(function (a, b) {
|
|
||||||
return b.count - a.count;
|
|
||||||
}),
|
|
||||||
appStore,
|
|
||||||
userAdminTeams,
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData, Page: AppsPage });
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import SetNewUserPassword from "@pages/auth/forgot-password/[id]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/forgot-password/[id]/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("reset_password"),
|
||||||
|
(t) => t("change_your_password")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: SetNewUserPassword,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import ForgotPassword from "@pages/auth/forgot-password";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/forgot-password/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("reset_password"),
|
||||||
|
(t) => t("change_your_password")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: ForgotPassword,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import Login from "@pages/auth/login";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/login/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${t("login")} | ${APP_NAME}`,
|
||||||
|
(t) => t("login")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Login,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Logout from "@pages/auth/logout";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/logout/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, Page: Logout, getData: withAppDirSsr(getServerSideProps) })<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Setup from "@pages/auth/setup";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/setup/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Setup,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import signin from "@pages/auth/signin";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/signin/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: signin,
|
||||||
|
// @ts-expect-error TODO: fix this
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Provider from "@pages/auth/sso/[provider]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Provider,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import DirectSSOLogin from "@pages/auth/sso/direct";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: DirectSSOLogin,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import VerifyEmailPage from "@pages/auth/verify-email";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("check_your_email"),
|
||||||
|
(t) => t("check_your_email")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: VerifyEmailPage,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import VerifyPage from "@pages/auth/verify";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/verify/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: VerifyPage,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
import OldPage from "@pages/booking/[uid]";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getData } from "../page";
|
import OldPage from "~/bookings/views/bookings-single-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
// @ts-expect-error Type '(context: GetServerSidePropsContext) => Promise<any>' is not assignable to type '(arg: {
|
|
||||||
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
|
||||||
@@ -1,204 +1,37 @@
|
|||||||
import OldPage from "@pages/booking/[uid]";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { cookies, headers } from "next/headers";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import { parseRecurringEvent } from "@calcom/lib";
|
|
||||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
|
||||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
||||||
|
|
||||||
import { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } from "@lib/booking";
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import OldPage from "~/bookings/views/bookings-single-view";
|
||||||
|
import { getServerSideProps, type PageProps } from "~/bookings/views/bookings-single-view.getServerSideProps";
|
||||||
|
|
||||||
const stringToBoolean = z
|
export const generateMetadata = async ({
|
||||||
.string()
|
params,
|
||||||
.optional()
|
searchParams,
|
||||||
.transform((val) => val === "true");
|
}: {
|
||||||
|
params: Params;
|
||||||
const querySchema = z.object({
|
searchParams: SearchParams;
|
||||||
uid: z.string(),
|
}) => {
|
||||||
email: z.string().optional(),
|
const { bookingInfo, eventType, recurringBookings } = await getData(
|
||||||
eventTypeSlug: z.string().optional(),
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
cancel: stringToBoolean,
|
|
||||||
allRemainingBookings: stringToBoolean,
|
|
||||||
changes: stringToBoolean,
|
|
||||||
reschedule: stringToBoolean,
|
|
||||||
isSuccessBookingPage: stringToBoolean,
|
|
||||||
formerTime: z.string().optional(),
|
|
||||||
seatReferenceUid: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
() => "",
|
|
||||||
() => ""
|
|
||||||
);
|
);
|
||||||
|
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
|
||||||
|
|
||||||
export const getData = async (context: GetServerSidePropsContext) => {
|
return await _generateMetadata(
|
||||||
const ssr = await ssrInit(context);
|
(t) =>
|
||||||
const session = await getServerSession(context);
|
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
|
||||||
let tz: string | null = null;
|
(t) =>
|
||||||
let userTimeFormat: number | null = null;
|
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`)
|
||||||
let requiresLoginToUpdate = false;
|
);
|
||||||
if (session) {
|
|
||||||
const user = await ssr.viewer.me.fetch();
|
|
||||||
tz = user.timeZone;
|
|
||||||
userTimeFormat = user.timeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedQuery = querySchema.safeParse(context.query);
|
|
||||||
|
|
||||||
if (!parsedQuery.success) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
|
|
||||||
|
|
||||||
const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid);
|
|
||||||
const bookingInfoRaw = await prisma.booking.findFirst({
|
|
||||||
where: {
|
|
||||||
uid: maybeUid,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
title: true,
|
|
||||||
id: true,
|
|
||||||
uid: true,
|
|
||||||
description: true,
|
|
||||||
customInputs: true,
|
|
||||||
smsReminderNumber: true,
|
|
||||||
recurringEventId: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
location: true,
|
|
||||||
status: true,
|
|
||||||
metadata: true,
|
|
||||||
cancellationReason: true,
|
|
||||||
responses: true,
|
|
||||||
rejectionReason: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
username: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
attendees: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eventTypeId: true,
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
eventName: true,
|
|
||||||
slug: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
seatsReferences: {
|
|
||||||
select: {
|
|
||||||
referenceUid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bookingInfoRaw) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventTypeRaw = !bookingInfoRaw.eventTypeId
|
|
||||||
? getDefaultEvent(eventTypeSlug || "")
|
|
||||||
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
|
|
||||||
if (!eventTypeRaw) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) {
|
|
||||||
requiresLoginToUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookingInfo = getBookingWithResponses(bookingInfoRaw);
|
|
||||||
// @NOTE: had to do this because Server side cant return [Object objects]
|
|
||||||
// probably fixable with json.stringify -> json.parse
|
|
||||||
bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date;
|
|
||||||
bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date;
|
|
||||||
|
|
||||||
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
|
|
||||||
? eventTypeRaw.hosts.map((host) => host.user)
|
|
||||||
: eventTypeRaw.users;
|
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length) {
|
|
||||||
if (!eventTypeRaw.owner) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
eventTypeRaw.users.push({
|
|
||||||
...eventTypeRaw.owner,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventType = {
|
|
||||||
...eventTypeRaw,
|
|
||||||
periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null,
|
|
||||||
periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null,
|
|
||||||
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata),
|
|
||||||
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
|
|
||||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
|
||||||
};
|
|
||||||
|
|
||||||
const profile = {
|
|
||||||
name: eventType.team?.name || eventType.users[0]?.name || null,
|
|
||||||
email: eventType.team ? null : eventType.users[0].email || null,
|
|
||||||
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
|
||||||
brandColor: eventType.team ? null : eventType.users[0].brandColor || null,
|
|
||||||
darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null,
|
|
||||||
slug: eventType.team?.slug || eventType.users[0]?.username || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bookingInfo !== null && eventType.seatsPerTimeSlot) {
|
|
||||||
await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payment = await prisma.payment.findFirst({
|
|
||||||
where: {
|
|
||||||
bookingId: bookingInfo.id,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
success: true,
|
|
||||||
refunded: true,
|
|
||||||
currency: true,
|
|
||||||
amount: true,
|
|
||||||
paymentOption: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
|
||||||
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
|
|
||||||
profile,
|
|
||||||
eventType,
|
|
||||||
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
|
||||||
bookingInfo,
|
|
||||||
paymentStatus: payment,
|
|
||||||
...(tz && { tz }),
|
|
||||||
userTimeFormat,
|
|
||||||
requiresLoginToUpdate,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-expect-error Argument of type '{ req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
|
||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
|
||||||
|
|
||||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
|
||||||
|
|
||||||
const querySchema = z.object({
|
|
||||||
status: z.enum(validStatuses),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
(t) => `${APP_NAME} | ${t("bookings")}`,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
export const generateStaticParams = async () => {
|
|
||||||
return validStatuses.map((status) => ({ status }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const parsedParams = querySchema.safeParse(ctx.params);
|
|
||||||
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ssg = await ssgInit(ctx);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: parsedParams.data.status,
|
|
||||||
dehydratedState: ssg.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData })<"L">;
|
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
|
||||||
@@ -1 +1,27 @@
|
|||||||
export { default } from "@pages/bookings/[status]";
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetStaticPropsType } from "next";
|
||||||
|
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { validStatuses } from "~/bookings/lib/validStatuses";
|
||||||
|
import Page from "~/bookings/views/bookings-listing-view";
|
||||||
|
import { getStaticProps } from "~/bookings/views/bookings-listing-view.getStaticProps";
|
||||||
|
|
||||||
|
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
|
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => `${APP_NAME} | ${t("bookings")}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
export const generateStaticParams = async () => {
|
||||||
|
return validStatuses.map((status) => ({ status }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, Page })<"P">;
|
||||||
|
|
||||||
|
export const dynamic = "force-static";
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import LegacyPage from "@pages/connect-and-join";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, Page: LegacyPage })<"P">;
|
||||||
@@ -1,27 +1,21 @@
|
|||||||
import LegacyPage, { type PageProps } from "@pages/d/[link]/[slug]";
|
import LegacyPage from "@pages/d/[link]/[slug]";
|
||||||
import { withAppDir } from "app/AppDirSSRHOC";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import slugify from "@calcom/lib/slugify";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
searchParams,
|
||||||
const pageProps = await getPageProps(
|
}: {
|
||||||
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
params: Params;
|
||||||
);
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const pageProps = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||||
|
|
||||||
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
||||||
const rescheduleUid = booking?.uid;
|
const rescheduleUid = booking?.uid;
|
||||||
@@ -38,95 +32,5 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getPageProps(context: GetServerSidePropsContext) {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const session = await getServerSession({ req: context.req });
|
|
||||||
const { link, slug } = paramsSchema.parse(context.params);
|
|
||||||
const { rescheduleUid, duration: queryDuration } = context.query;
|
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
|
||||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
|
||||||
|
|
||||||
const hashedLink = await prisma.hashedLink.findUnique({
|
|
||||||
where: {
|
|
||||||
link,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
eventTypeId: true,
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
users: {
|
|
||||||
select: {
|
|
||||||
username: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const username = hashedLink?.eventType.users[0]?.username;
|
|
||||||
|
|
||||||
if (!hashedLink || !username) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username,
|
|
||||||
organization: isValidOrgDomain
|
|
||||||
? {
|
|
||||||
slug: currentOrgDomain,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
away: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
let booking: GetBookingType | null = null;
|
|
||||||
if (rescheduleUid) {
|
|
||||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent, org });
|
|
||||||
|
|
||||||
if (!eventData) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
entity: eventData.entity,
|
|
||||||
duration: getMultipleDurationValue(eventData.metadata?.multipleDuration, queryDuration, eventData.length),
|
|
||||||
booking,
|
|
||||||
away: user?.away,
|
|
||||||
user: username,
|
|
||||||
slug,
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
isBrandingHidden: user?.hideBranding,
|
|
||||||
// Sending the team event from the server, because this template file
|
|
||||||
// is reused for both team and user events.
|
|
||||||
isTeamEvent,
|
|
||||||
hashedLink: link,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramsSchema = z.object({ link: z.string(), slug: z.string().transform((s) => slugify(s)) });
|
|
||||||
|
|
||||||
// @ts-expect-error arg
|
|
||||||
const getData = withAppDir<PageProps>(getPageProps);
|
|
||||||
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout })<"L">;
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
import EnterprisePage from "@components/EnterprisePage";
|
import EnterprisePage from "@components/EnterprisePage";
|
||||||
|
|
||||||
@@ -8,4 +11,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("create_your_org_description")
|
(t) => t("create_your_org_description")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default EnterprisePage;
|
export default WithLayout({ getLayout, Page: EnterprisePage })<"P">;
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import EventTypePageWrapper from "~/event-types/views/event-types-single-view";
|
||||||
|
import { getServerSideProps } from "~/event-types/views/event-types-single-view.getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const { eventType } = await getData(legacyCtx);
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${eventType.title} | ${t("event_type")} | ${APP_NAME}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, Page: EventTypePageWrapper })<"P">;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout })<"L">;
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import EventTypes from "@pages/event-types";
|
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import EventTypes from "~/event-types/views/event-types-listing-view";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -7,4 +9,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("event_types_page_subtitle")
|
(t) => t("event_types_page_subtitle")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default EventTypes;
|
export default WithLayout({ getLayout: null, Page: EventTypes })<"P">;
|
||||||
Loaded 100 of 1043 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user