Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02d61da9b0 | ||
|
|
c0017114ef | ||
|
|
4b99072bcc | ||
|
|
821dce6a04 | ||
|
|
3860717ae3 | ||
|
|
f4303d8d60 | ||
|
|
851aa30829 | ||
|
|
2664eb459b | ||
|
|
ce997447a4 | ||
|
|
5fa76f943b | ||
|
|
4226231b8a | ||
|
|
69695d3022 | ||
|
|
c13fce4483 | ||
|
|
f32ea8b4c5 | ||
|
|
36cf6e3703 | ||
|
|
455973b394 | ||
|
|
0b73491367 | ||
|
|
573756eaa2 | ||
|
|
5c686d90f9 | ||
|
|
9ad8aa981a | ||
|
|
bb9581d0d9 | ||
|
|
75d611c2e8 | ||
|
|
0408ce0741 | ||
|
|
5818da414b | ||
|
|
d381c2c2cb | ||
|
|
3aa6499cd4 | ||
|
|
1e67f98970 | ||
|
|
aaf5958b7d | ||
|
|
80941c17a0 | ||
|
|
829d6c4714 | ||
|
|
fd6e2a9ca5 | ||
|
|
383314d3e2 | ||
|
|
cee3195708 | ||
|
|
7c66f33de2 | ||
|
|
32d97fbea4 | ||
|
|
a20ad44209 | ||
|
|
a6d7e4f107 | ||
|
|
5f21f54bae | ||
|
|
3a6ca9ef15 | ||
|
|
95688b38fa | ||
|
|
8dbe3848e7 | ||
|
|
9202028ab6 | ||
|
|
2f90138757 | ||
|
|
41dcfe0307 | ||
|
|
913eb47ff1 | ||
|
|
e879c923ea | ||
|
|
ae2ea190d8 | ||
|
|
5c9a5c5729 | ||
|
|
77184226e5 | ||
|
|
a4d4626319 | ||
|
|
3e063d6744 | ||
|
|
0413783ffb | ||
|
|
92f13cdb56 | ||
|
|
1e295827c7 | ||
|
|
c7cd25b7d5 | ||
|
|
d24807e0e1 | ||
|
|
6840642cb7 | ||
|
|
d5c2212c41 | ||
|
|
c5cb2edd65 | ||
|
|
023dfc2a5e | ||
|
|
dd4e1ba11b | ||
|
|
70d5a78d69 | ||
|
|
ea2036672b | ||
|
|
77a0d69c1a | ||
|
|
cf6581eaea | ||
|
|
c36f4d4a65 | ||
|
|
dd4e8344c9 | ||
|
|
6fb42b63e0 | ||
|
|
b6df61d287 | ||
|
|
77f59b1e4d | ||
|
|
c9bb03b602 | ||
|
|
b517adfb66 | ||
|
|
ac90caa81b | ||
|
|
4c0b0115a2 | ||
|
|
ec87eee900 | ||
|
|
aed80cdbe1 | ||
|
|
e3eaa69339 | ||
|
|
fc0a2b5a37 | ||
|
|
f7dee536d7 | ||
|
|
a8e4097c4c | ||
|
|
0994050b0f | ||
|
|
3e7e376100 | ||
|
|
93cac5a1c9 | ||
|
|
a03e64da1b | ||
|
|
2e571eaedf | ||
|
|
258b1a7bb8 | ||
|
|
d675f82557 | ||
|
|
312733ce68 | ||
|
|
58798f27d2 | ||
|
|
478ca05246 | ||
|
|
f0ffc24d39 | ||
|
|
5eacd6b807 | ||
|
|
98ef45be18 | ||
|
|
1e98652161 | ||
|
|
7a6f4b2594 | ||
|
|
c826e9cea8 | ||
|
|
784db75b38 | ||
|
|
75962f7719 | ||
|
|
f24f5b2b35 | ||
|
|
08edd4f361 | ||
|
|
8b9c3dcbb9 | ||
|
|
539214b164 | ||
|
|
9d9a85ddd9 | ||
|
|
560598db56 | ||
|
|
3dd0b2d46b | ||
|
|
bf4cdf6245 | ||
|
|
eecf9e1ab9 | ||
|
|
2b4be9c34e | ||
|
|
9c4ed1a303 | ||
|
|
6aa1dbad90 | ||
|
|
7a9462b756 | ||
|
|
46b1bc3365 | ||
|
|
f86a8dc93e | ||
|
|
d9b49b2c41 | ||
|
|
728f55e4bc | ||
|
|
d3e7554835 | ||
|
|
cbb56f6a61 | ||
|
+2 |
2d514a2a3e | ||
|
|
49e392423a | ||
|
|
32ecb924d7 | ||
|
|
e4ead13b3b | ||
|
|
f6d3b11582 | ||
|
|
2a26a591a8 | ||
|
|
4819be0093 | ||
|
|
a9314471b4 | ||
|
|
52cff6882c | ||
|
|
d0e942b30b | ||
|
|
e995e2439f | ||
|
|
b367e153de | ||
|
|
bd361844d6 | ||
|
|
a12ab5bb65 | ||
|
|
2b7c087ac8 | ||
|
|
71729813a9 | ||
|
|
0500056831 | ||
|
|
a97092619f | ||
|
|
8666efa29d | ||
|
|
87a2f855f8 | ||
|
|
0e3b9f08b8 | ||
|
|
02a334e072 | ||
|
|
3a5c97553d | ||
|
|
5b1ee1a7ac | ||
|
|
67faf454d9 | ||
|
|
0330d14210 | ||
|
|
d865f6cb5f | ||
|
|
51639e38d7 | ||
|
|
c380ea810c | ||
|
|
56b29b5df6 | ||
|
|
b6c243f1dd | ||
|
|
b78ca7be2a | ||
|
|
f3a37886e3 | ||
|
|
3cb59b7874 |
+8
-1
@@ -94,7 +94,7 @@ CRON_ENABLE_APP_SYNC=false
|
||||
|
||||
# Application Key for symmetric encryption and decryption
|
||||
# must be 32 bytes for AES256 encryption algorithm
|
||||
# You can use: `openssl rand -base64 32` to generate one
|
||||
# You can use: `openssl rand -base64 24` to generate a 32-character key
|
||||
CALENDSO_ENCRYPTION_KEY=
|
||||
|
||||
# Intercom Config
|
||||
@@ -509,3 +509,10 @@ TRIGGER_DEV_PROJECT_REF=
|
||||
|
||||
GOOGLE_ADS_ENABLED=1 # To enable Google Ads tracking (gclid)
|
||||
LINKEDIN_ADS_ENABLED=1 # To enable LinkedIn Ads tracking (li_fat_id)
|
||||
|
||||
# - BACKBLAZE B2 (Compliance Documents) *************************************************************************
|
||||
# Used for storing and serving compliance documents (SOC 2, ISO 27001, pentest reports)
|
||||
B2_APPLICATION_KEY_ID=
|
||||
B2_APPLICATION_KEY=
|
||||
B2_BUCKET_ID=
|
||||
B2_BUCKET_NAME=
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
name: Cache Git Checkout
|
||||
description: "Cache or restore git checkout to speed up CI workflows"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Cache git checkout
|
||||
uses: actions/cache@v4
|
||||
id: cache-checkout
|
||||
with:
|
||||
path: |
|
||||
.
|
||||
!.git
|
||||
!node_modules
|
||||
!**/node_modules
|
||||
!apps/web/public/app-store
|
||||
key: git-checkout-${{ github.head_ref || github.ref_name }}-${{ github.event.pull_request.head.sha || github.sha }}
|
||||
- name: Checkout PR code (on cache miss)
|
||||
if: steps.cache-checkout.outputs.cache-hit != 'true'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
||||
fetch-depth: 2
|
||||
sparse-checkout-cone-mode: false
|
||||
sparse-checkout: |
|
||||
/*
|
||||
!/example-apps/
|
||||
!**/*.mp4
|
||||
!/docs/images/
|
||||
!packages/app-store/*/static/*.png
|
||||
!packages/app-store/*/static/*.jpg
|
||||
!packages/app-store/*/static/*.jpeg
|
||||
!packages/app-store/*/static/*.gif
|
||||
@@ -1,10 +0,0 @@
|
||||
name: Dangerous git Checkout
|
||||
description: "Git Checkout from PR code so we can run checks from forks"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
@@ -2,3 +2,9 @@ GET /v2/event-types/{eventTypeId} added to the 'data' response property 'oneOf'
|
||||
GET /v2/event-types/{eventTypeId} added '#/components/schemas/EventTypeOutput_2024_06_14, #/components/schemas/TeamEventTypeOutput_2024_06_14' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
GET /v2/bookings/{bookingUid}/recordings added the new required 'header' request parameter 'Authorization'
|
||||
GET /v2/bookings/{bookingUid}/transcripts added the new required 'header' request parameter 'Authorization'
|
||||
POST /v2/bookings added 'subschema #2, subschema #4' to the 'data' response property 'oneOf' list for the response status '201'
|
||||
GET /v2/bookings/{bookingUid} added 'subschema #3, subschema #6' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
POST /v2/bookings/{bookingUid}/cancel added 'subschema #3, subschema #6' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
POST /v2/bookings/{bookingUid}/confirm added 'subschema #3, subschema #6' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
POST /v2/bookings/{bookingUid}/decline added 'subschema #3, subschema #6' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
POST /v2/bookings/{bookingUid}/guests added 'subschema #3, subschema #6' to the 'data' response property 'oneOf' list for the response status '200'
|
||||
@@ -65,7 +65,9 @@ jobs:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/cache-db
|
||||
- name: Cache API v1 production build
|
||||
|
||||
@@ -35,7 +35,9 @@ jobs:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Generate Prisma schemas
|
||||
working-directory: apps/api/v2
|
||||
|
||||
@@ -12,7 +12,9 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- run: yarn prisma generate
|
||||
- name: Run API v2 unit tests
|
||||
|
||||
@@ -12,7 +12,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Cache atoms production build
|
||||
uses: actions/cache@v4
|
||||
|
||||
@@ -34,7 +34,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
|
||||
- name: Generate Swagger
|
||||
|
||||
@@ -12,7 +12,9 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Show info
|
||||
run: node -e "console.log(require('v8').getHeapStatistics())"
|
||||
|
||||
@@ -17,7 +17,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
name: Cubic AI to Devin Review
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
trigger-devin:
|
||||
name: Trigger Devin to Address Cubic AI Review
|
||||
# Only run if the review is from cubic-dev-ai bot and contains the AI prompt
|
||||
if: github.event.review.user.login == 'cubic-dev-ai[bot]'
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Extract AI prompt from Cubic review
|
||||
id: extract-prompt
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const reviewBody = context.payload.review.body || '';
|
||||
|
||||
// Look for the AI prompt section in Cubic's review
|
||||
// Format: <details><summary>Prompt for AI agents (all issues)</summary>
|
||||
// ```text
|
||||
// ... prompt content ...
|
||||
// ```
|
||||
// </details>
|
||||
const promptMatch = reviewBody.match(/<details>\s*<summary>Prompt for AI agents[^<]*<\/summary>\s*```(?:text)?\s*([\s\S]*?)```\s*<\/details>/i);
|
||||
|
||||
if (!promptMatch || !promptMatch[1]) {
|
||||
console.log('No AI prompt found in review body');
|
||||
console.log('Review body:', reviewBody);
|
||||
core.setOutput('has-prompt', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const prompt = promptMatch[1].trim();
|
||||
console.log('Extracted prompt:', prompt);
|
||||
|
||||
// Save prompt to file to avoid shell escaping issues
|
||||
const fs = require('fs');
|
||||
fs.writeFileSync('/tmp/cubic-prompt.txt', prompt);
|
||||
|
||||
core.setOutput('has-prompt', 'true');
|
||||
|
||||
- name: Check for existing Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true'
|
||||
id: check-session
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
with:
|
||||
script: |
|
||||
async function checkSessionStatus(sessionId) {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
// If PR was created through Devin, always use that session
|
||||
const prBody = context.payload.pull_request.body || '';
|
||||
const prSessionMatch = prBody.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
|
||||
if (prSessionMatch) {
|
||||
const sessionId = prSessionMatch[1];
|
||||
console.log(`PR was created by Devin session: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
console.log(`PR creator session status: ${session.status_enum}`);
|
||||
core.exportVariable('EXISTING_SESSION_ID', sessionId);
|
||||
core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
core.setOutput('has-active-session', 'true');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// PR was not created through Devin - check for existing Devin session from PR comments
|
||||
// Look for any Devin session comment (cubic review, conflict resolution, stale PR completion, etc.)
|
||||
const devinSessionPatterns = [
|
||||
'Devin AI is addressing Cubic AI',
|
||||
'Devin AI is resolving merge conflicts',
|
||||
'Devin AI is completing this stale PR'
|
||||
];
|
||||
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number
|
||||
});
|
||||
|
||||
let sessionId = null;
|
||||
for (const comment of comments.data.reverse()) {
|
||||
const hasDevinSession = devinSessionPatterns.some(pattern => comment.body.includes(pattern));
|
||||
if (hasDevinSession) {
|
||||
const match = comment.body.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
sessionId = match[1];
|
||||
console.log(`Found existing Devin session from comment: ${sessionId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
console.log('No existing Devin session found in PR comments');
|
||||
core.setOutput('has-active-session', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found session ID from comment: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (!session) {
|
||||
core.setOutput('has-active-session', 'false');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeStatuses = ['working', 'blocked', 'resumed'];
|
||||
|
||||
if (activeStatuses.includes(session.status_enum)) {
|
||||
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
|
||||
core.exportVariable('EXISTING_SESSION_ID', sessionId);
|
||||
core.exportVariable('SESSION_URL', `https://app.devin.ai/sessions/${sessionId}`);
|
||||
core.setOutput('has-active-session', 'true');
|
||||
} else {
|
||||
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
|
||||
core.setOutput('has-active-session', 'false');
|
||||
}
|
||||
|
||||
- name: Send message to existing Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'true'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
run: |
|
||||
CUBIC_PROMPT=$(cat /tmp/cubic-prompt.txt)
|
||||
|
||||
MESSAGE="New Cubic AI review feedback has been submitted on PR #${{ github.event.pull_request.number }}.
|
||||
|
||||
IMPORTANT: Before fixing any issue, check the individual Cubic comment on the PR for that issue's confidence score. The confidence score appears as 'Fix confidence (alpha): X/10' in each comment. Note that the (alpha) may or not be there. Don't be picky about that text. But only fix issues where the confidence score is 9/10 or higher. If you cannot find a confidence score for an issue, skip it.
|
||||
|
||||
Here are the issues identified by Cubic AI:
|
||||
|
||||
${CUBIC_PROMPT}
|
||||
|
||||
Continue working on the same PR branch and push your fixes."
|
||||
|
||||
HTTP_CODE=$(curl -s -o /tmp/devin-response.json -w "%{http_code}" -X POST "https://api.devin.ai/v1/sessions/${EXISTING_SESSION_ID}/message" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg message "$MESSAGE" '{message: $message}')")
|
||||
|
||||
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
|
||||
echo "Failed to send message to Devin session: HTTP $HTTP_CODE"
|
||||
cat /tmp/devin-response.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Message sent to existing session successfully"
|
||||
|
||||
- name: Create new Devin session
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && steps.check-session.outputs.has-active-session == 'false'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
run: |
|
||||
CUBIC_PROMPT=$(cat /tmp/cubic-prompt.txt)
|
||||
|
||||
FULL_PROMPT="You are addressing code review feedback from Cubic AI on PR #${{ github.event.pull_request.number }} in repository ${{ github.repository }}.
|
||||
|
||||
Your tasks:
|
||||
1. Clone the repository ${{ github.repository }} locally.
|
||||
2. Check out the PR branch and review the current code.
|
||||
3. Read the Cubic AI review feedback below AND check each individual Cubic comment on the PR for the confidence score.
|
||||
4. IMPORTANT: Only fix issues where the confidence score is 9/10 or higher. The confidence score appears as 'Fix confidence (alpha): X/10' in each Cubic comment. If you cannot find a confidence score for an issue, skip it.
|
||||
5. Commit your changes with clear commit messages referencing the issues fixed.
|
||||
6. Push your changes to the PR branch.
|
||||
|
||||
Cubic AI Review Feedback:
|
||||
${CUBIC_PROMPT}
|
||||
|
||||
Rules and Guidelines:
|
||||
1. Make minimal, focused changes that directly address the feedback.
|
||||
2. Follow the existing code style and conventions in the repository.
|
||||
3. Test your changes if possible before pushing.
|
||||
4. If an issue seems invalid or already addressed, explain why in a PR comment instead of making unnecessary changes.
|
||||
5. Never ask for user confirmation. Never wait for user messages."
|
||||
|
||||
HTTP_CODE=$(curl -s -o /tmp/devin-response.json -w "%{http_code}" -X POST "https://api.devin.ai/v1/sessions" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg prompt "$FULL_PROMPT" \
|
||||
--arg title "Cubic AI Review: PR #${{ github.event.pull_request.number }}" \
|
||||
'{
|
||||
prompt: $prompt,
|
||||
title: $title,
|
||||
tags: ["cubic-ai-review", "pr-${{ github.event.pull_request.number }}"]
|
||||
}')")
|
||||
|
||||
if [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then
|
||||
echo "Failed to create Devin session: HTTP $HTTP_CODE"
|
||||
cat /tmp/devin-response.json
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SESSION_URL=$(cat /tmp/devin-response.json | jq -r '.url // .session_url // empty')
|
||||
if [ -n "$SESSION_URL" ]; then
|
||||
echo "Devin session created: $SESSION_URL"
|
||||
echo "SESSION_URL=$SESSION_URL" >> $GITHUB_ENV
|
||||
echo "NEW_SESSION=true" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Post comment with Devin session link
|
||||
if: steps.extract-prompt.outputs.has-prompt == 'true' && env.SESSION_URL != ''
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const sessionUrl = process.env.SESSION_URL;
|
||||
const isNewSession = process.env.NEW_SESSION === 'true';
|
||||
|
||||
const message = isNewSession
|
||||
? `### Devin AI is addressing Cubic AI's review feedback\n\nA Devin session has been created to address the issues identified by Cubic AI.\n\n[View Devin Session](${sessionUrl})`
|
||||
: `### Devin AI is addressing Cubic AI's review feedback\n\nNew feedback has been sent to the existing Devin session.\n\n[View Devin Session](${sessionUrl})`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: message
|
||||
});
|
||||
@@ -31,3 +31,13 @@ jobs:
|
||||
with:
|
||||
key: ${{ env.CACHE_NAME }}-${{ github.event.pull_request.head.ref }}
|
||||
prefix: "true"
|
||||
|
||||
delete-git-checkout-cache-on-pr-close:
|
||||
if: github.event_name == 'pull_request' && github.event.action == 'closed'
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Delete git-checkout cache
|
||||
uses: useblacksmith/cache-delete@v1
|
||||
with:
|
||||
key: git-checkout-${{ github.event.pull_request.head.ref }}-
|
||||
prefix: "true"
|
||||
|
||||
@@ -0,0 +1,477 @@
|
||||
name: Devin PR Conflict Resolver
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [labeled]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to resolve conflicts for'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
resolve-conflicts:
|
||||
name: Resolve PR Conflicts with Devin
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
# Only run when devin-conflict-resolution label is added, or on manual dispatch
|
||||
if: |
|
||||
(github.event_name == 'pull_request_target' && github.event.label.name == 'devin-conflict-resolution') ||
|
||||
github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- name: Get PR details
|
||||
id: get-pr
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
let prNumber;
|
||||
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
prNumber = parseInt(process.env.INPUT_PR_NUMBER, 10);
|
||||
if (!prNumber) {
|
||||
core.setFailed('PR number is required for manual dispatch');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
prNumber = context.payload.pull_request.number;
|
||||
}
|
||||
|
||||
console.log(`Processing PR #${prNumber}`);
|
||||
|
||||
// Get PR details via GraphQL for complete info
|
||||
const query = `
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
number
|
||||
title
|
||||
mergeable
|
||||
isDraft
|
||||
headRefName
|
||||
baseRefName
|
||||
url
|
||||
body
|
||||
headRepository {
|
||||
owner {
|
||||
login
|
||||
}
|
||||
name
|
||||
}
|
||||
maintainerCanModify
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const result = await github.graphql(query, { owner, repo, number: prNumber });
|
||||
const pr = result.repository.pullRequest;
|
||||
|
||||
if (!pr) {
|
||||
core.setFailed(`PR #${prNumber} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
const isFork = pr.headRepository?.owner?.login !== owner;
|
||||
|
||||
// Check if fork PR has maintainer access
|
||||
if (isFork && !pr.maintainerCanModify) {
|
||||
console.log(`PR #${prNumber} is from a fork without maintainer access`);
|
||||
core.setOutput('needs-maintainer-access', 'true');
|
||||
core.setOutput('fork-author', pr.headRepository?.owner?.login);
|
||||
} else {
|
||||
core.setOutput('needs-maintainer-access', 'false');
|
||||
}
|
||||
|
||||
const headRepoOwner = pr.headRepository?.owner?.login || owner;
|
||||
const headRepoName = pr.headRepository?.name || repo;
|
||||
|
||||
const prData = {
|
||||
number: pr.number,
|
||||
title: pr.title,
|
||||
head_ref: pr.headRefName,
|
||||
base_ref: pr.baseRefName,
|
||||
html_url: pr.url,
|
||||
body: pr.body,
|
||||
is_fork: isFork,
|
||||
head_repo_owner: headRepoOwner,
|
||||
head_repo_name: headRepoName,
|
||||
mergeable: pr.mergeable
|
||||
};
|
||||
|
||||
const fs = require('fs');
|
||||
fs.writeFileSync('/tmp/pr-data.json', JSON.stringify(prData));
|
||||
|
||||
core.setOutput('pr-number', prNumber.toString());
|
||||
core.setOutput('has-pr', 'true');
|
||||
core.setOutput('mergeable-status', pr.mergeable || 'UNKNOWN');
|
||||
core.setOutput('has-conflicts', pr.mergeable === 'CONFLICTING' ? 'true' : 'false');
|
||||
|
||||
console.log(`PR #${prNumber}: ${pr.title}`);
|
||||
console.log(`Mergeable status: ${pr.mergeable}`);
|
||||
console.log(`Is fork: ${isFork}`);
|
||||
console.log(`Has conflicts: ${pr.mergeable === 'CONFLICTING'}`);
|
||||
|
||||
- name: Request maintainer access for fork PR
|
||||
if: steps.get-pr.outputs.needs-maintainer-access == 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = parseInt('${{ steps.get-pr.outputs.pr-number }}', 10);
|
||||
const forkAuthor = '${{ steps.get-pr.outputs.fork-author }}';
|
||||
|
||||
console.log(`Requesting maintainer access for PR #${prNumber}`);
|
||||
|
||||
const commentBody = [
|
||||
'### Maintainer Access Needed',
|
||||
'',
|
||||
`Hi @${forkAuthor}! Thanks for your contribution to Cal.com.`,
|
||||
'',
|
||||
'We noticed that this PR doesn\'t have "Allow edits from maintainers" enabled. We\'d love to help keep your PR up to date by resolving merge conflicts and making small fixes when needed.',
|
||||
'',
|
||||
'**Could you please enable this setting?** Here\'s how:',
|
||||
'1. Scroll down to the bottom of this PR page',
|
||||
'2. In the right sidebar, check the box that says **"Allow edits and access to secrets by maintainers"**',
|
||||
'',
|
||||
'This allows us to push commits directly to your PR branch, which helps us:',
|
||||
'- Resolve merge conflicts automatically',
|
||||
'- Make small adjustments to help get your PR merged faster',
|
||||
'',
|
||||
'Once you enable this setting, please remove and re-add the `devin-conflict-resolution` label to trigger conflict resolution.',
|
||||
'',
|
||||
'If you have any concerns about enabling this setting, feel free to let us know!'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
// Add label to track that we've requested access
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['maintainer-access-requested']
|
||||
});
|
||||
|
||||
// Remove the devin-conflict-resolution label since we can't proceed
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: 'devin-conflict-resolution'
|
||||
});
|
||||
|
||||
console.log(`Posted comment and updated labels for PR #${prNumber}`);
|
||||
core.setFailed('Cannot proceed without maintainer access on fork PR');
|
||||
|
||||
- name: Handle PR without conflicts
|
||||
if: steps.get-pr.outputs.has-pr == 'true' && steps.get-pr.outputs.needs-maintainer-access != 'true' && steps.get-pr.outputs.has-conflicts != 'true'
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = parseInt('${{ steps.get-pr.outputs.pr-number }}', 10);
|
||||
const mergeableStatus = '${{ steps.get-pr.outputs.mergeable-status }}';
|
||||
|
||||
console.log(`PR #${prNumber} does not have conflicts (status: ${mergeableStatus})`);
|
||||
|
||||
let message;
|
||||
if (mergeableStatus === 'MERGEABLE') {
|
||||
message = `### No Conflicts Detected\n\nThis PR does not have any merge conflicts with the base branch. The \`devin-conflict-resolution\` label has been removed.\n\nIf you believe this PR should have conflicts, please check the PR status and try again.`;
|
||||
} else {
|
||||
message = `### Mergeable Status Unknown\n\nGitHub is still computing the mergeable status for this PR (current status: \`${mergeableStatus}\`). The \`devin-conflict-resolution\` label has been removed.\n\nPlease wait a moment and re-add the label if the PR has conflicts.`;
|
||||
}
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: message
|
||||
});
|
||||
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
name: 'devin-conflict-resolution'
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(`Label may have already been removed: ${e.message}`);
|
||||
}
|
||||
|
||||
console.log(`Posted comment and removed label for PR #${prNumber}`);
|
||||
|
||||
- name: Create Devin session for conflict resolution
|
||||
if: steps.get-pr.outputs.has-pr == 'true' && steps.get-pr.outputs.needs-maintainer-access != 'true' && steps.get-pr.outputs.has-conflicts == 'true'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const pr = JSON.parse(fs.readFileSync('/tmp/pr-data.json', 'utf8'));
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
async function checkSessionStatus(sessionId) {
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${sessionId}`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(`Failed to fetch session ${sessionId}: ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
async function findExistingSession(prNumber, prBody) {
|
||||
// First check if PR was created through Devin
|
||||
const prSessionMatch = prBody?.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (prSessionMatch) {
|
||||
const sessionId = prSessionMatch[1];
|
||||
console.log(`PR #${prNumber} was created by Devin session: ${sessionId}`);
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
return { sessionId, sessionUrl: `https://app.devin.ai/sessions/${sessionId}`, isFromPrBody: true };
|
||||
}
|
||||
}
|
||||
|
||||
// Check PR comments for existing Devin session (conflict resolution or Cubic AI review)
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const sessionPatterns = [
|
||||
'Devin AI is resolving merge conflicts',
|
||||
'Devin AI is addressing Cubic AI'
|
||||
];
|
||||
|
||||
for (const comment of comments.data.reverse()) {
|
||||
const hasDevinSession = sessionPatterns.some(pattern => comment.body?.includes(pattern));
|
||||
if (hasDevinSession) {
|
||||
const match = comment.body?.match(/app\.devin\.ai\/sessions\/([a-f0-9-]+)/);
|
||||
if (match) {
|
||||
const sessionId = match[1];
|
||||
console.log(`Found existing session from comment: ${sessionId}`);
|
||||
|
||||
const session = await checkSessionStatus(sessionId);
|
||||
if (session) {
|
||||
const activeStatuses = ['working', 'blocked', 'resumed'];
|
||||
if (activeStatuses.includes(session.status_enum)) {
|
||||
console.log(`Session ${sessionId} is active (status: ${session.status_enum})`);
|
||||
return { sessionId, sessionUrl: `https://app.devin.ai/sessions/${sessionId}`, isFromPrBody: false };
|
||||
} else {
|
||||
console.log(`Session ${sessionId} is not active (status: ${session.status_enum})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(`Processing PR #${pr.number}: ${pr.title}${pr.is_fork ? ' (fork)' : ''}`);
|
||||
|
||||
const existingSession = await findExistingSession(pr.number, pr.body);
|
||||
|
||||
const forkInstructions = pr.is_fork ? `
|
||||
IMPORTANT: This PR is from a fork.
|
||||
- Clone the FORK repository: ${pr.head_repo_owner}/${pr.head_repo_name}
|
||||
- The branch to work on is: ${pr.head_ref}
|
||||
- Add the upstream remote for ${owner}/${repo}
|
||||
- Fetch and merge the upstream ${pr.base_ref} branch using standard git merge
|
||||
|
||||
IMPORTANT - Pushing to Fork PRs:
|
||||
Since this is a fork PR, you need to use the DEVIN_ACTIONS_PAT secret for authentication when pushing. Configure git to use this PAT for authentication before pushing your changes to the fork.` : `
|
||||
- Clone the repository: ${owner}/${repo}
|
||||
- Check out the PR branch: ${pr.head_ref}
|
||||
- Merge the base branch (${pr.base_ref}) using standard git merge`;
|
||||
|
||||
const conflictResolutionInstructions = `You are resolving merge conflicts on PR #${pr.number} in repository ${owner}/${repo}.
|
||||
|
||||
PR Title: ${pr.title}
|
||||
PR URL: ${pr.html_url}
|
||||
Head Branch: ${pr.head_ref}
|
||||
Base Branch: ${pr.base_ref}
|
||||
${pr.is_fork ? `Fork Repository: ${pr.head_repo_owner}/${pr.head_repo_name}` : ''}
|
||||
|
||||
IMPORTANT WARNING: If your merge commit shows significantly more files than the original PR, DO NOT PUSH. This indicates the merge was done incorrectly. Abort and leave a comment instead.
|
||||
|
||||
Your tasks:
|
||||
${forkInstructions}
|
||||
|
||||
Then:
|
||||
1. Resolve all merge conflicts carefully:
|
||||
- Review the conflicting changes from both branches
|
||||
- Make intelligent decisions about how to combine the changes
|
||||
- Preserve the intent of both the PR changes and the base branch updates
|
||||
- If unsure about a conflict, prefer keeping both changes where possible
|
||||
2. Test that the code still works after resolving conflicts (run lint/type checks).
|
||||
3. Commit the merge resolution with a clear commit message.
|
||||
4. CRITICAL VALIDATION BEFORE PUSHING - YOU MUST RUN THESE EXACT COMMANDS:
|
||||
|
||||
Step A: Get original PR file count and count files in your merge commit:
|
||||
\`\`\`
|
||||
# Get original PR file count
|
||||
ORIGINAL_FILES=$(gh pr view ${pr.number} --json files -q '.files | length')
|
||||
echo "Original PR files: $ORIGINAL_FILES"
|
||||
|
||||
# Count files in merge commit (what the PR contributes)
|
||||
# HEAD^2 = base branch parent, so HEAD^2...HEAD shows the PR's contribution
|
||||
MERGE_FILES=$(git diff --name-only HEAD^2...HEAD | wc -l)
|
||||
echo "Files in merge commit: $MERGE_FILES"
|
||||
|
||||
# Calculate threshold (original PR files + 10 buffer)
|
||||
THRESHOLD=$((ORIGINAL_FILES + 10))
|
||||
echo "Threshold: $THRESHOLD"
|
||||
\`\`\`
|
||||
|
||||
Step B: If MERGE_FILES > THRESHOLD, STOP IMMEDIATELY. DO NOT PUSH.
|
||||
- A proper merge commit should show approximately the same files as the original PR
|
||||
- If you see significantly more files, the merge was done INCORRECTLY
|
||||
- This usually means you accidentally reproduced changes from ${pr.base_ref} instead of just resolving conflicts
|
||||
|
||||
Step C: Compare your merge to the original PR:
|
||||
\`\`\`
|
||||
git diff --stat HEAD^2...HEAD
|
||||
\`\`\`
|
||||
- This should show approximately the same files as the original PR (the PR's contribution to the base branch)
|
||||
- If you see many more files or files unrelated to the original PR, STOP
|
||||
|
||||
Step D: If validation fails:
|
||||
- DO NOT PUSH under any circumstances
|
||||
- DO NOT attempt to fix it
|
||||
- Leave a comment on the PR explaining that the merge validation failed
|
||||
- ABORT THE TASK IMMEDIATELY
|
||||
- It is better to leave the PR with conflicts than to push a broken merge commit that pollutes git history
|
||||
5. Push the resolved changes to the PR branch only after validation passes.
|
||||
6. After successfully pushing the resolved changes, remove the \`devin-conflict-resolution\` label from the PR using the GitHub API.
|
||||
|
||||
Rules and Guidelines:
|
||||
1. Be careful when resolving conflicts - understand the context of both changes.
|
||||
To help with this, try to find the recent PRs associated with the conflicting files to gain more context.
|
||||
2. Follow the existing code style and conventions in the repository.
|
||||
3. Run lint and type checks before pushing to ensure the code is valid.
|
||||
4. If a conflict seems too complex or risky to resolve automatically, explain the situation in a PR comment instead.
|
||||
5. Never ask for user confirmation. Never wait for user messages.
|
||||
6. CRITICAL: If this is a fork PR and you encounter ANY error when pushing (permission denied, authentication failure, etc.) even after using the DEVIN_ACTIONS_PAT, you MUST fail the task immediately. Do NOT attempt to push to a new branch in the main ${owner}/${repo} repository as a workaround. Simply report the error and stop.
|
||||
7. CRITICAL: Never reproduce or recreate changes from the target branch. Your merge commit should ONLY contain conflict resolutions. If you find yourself manually copying file contents from ${pr.base_ref} or creating changes that mirror what's already in ${pr.base_ref}, you are doing it wrong. Use git's merge functionality properly - it handles bringing in changes automatically.
|
||||
8. CRITICAL: If your merge commit shows significantly more files than the original PR (more than original PR files + 10), DO NOT PUSH UNDER ANY CIRCUMSTANCES. This is a sign that the merge was done incorrectly. Abort the task and leave a comment explaining the issue. A bad merge commit in the git history is worse than leaving the PR with conflicts.`;
|
||||
|
||||
try {
|
||||
let sessionUrl;
|
||||
let isNewSession = false;
|
||||
|
||||
if (existingSession) {
|
||||
console.log(`Sending message to existing session ${existingSession.sessionId} for PR #${pr.number}`);
|
||||
|
||||
const message = `PR #${pr.number} has merge conflicts that need to be resolved.
|
||||
|
||||
${conflictResolutionInstructions}
|
||||
|
||||
Continue working on the same PR branch and push your fixes.`;
|
||||
|
||||
const response = await fetch(`https://api.devin.ai/v1/sessions/${existingSession.sessionId}/message`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ message })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to send message to session ${existingSession.sessionId}: ${response.status}`);
|
||||
throw new Error(`Failed to send message to existing session: ${response.status}`);
|
||||
}
|
||||
|
||||
sessionUrl = existingSession.sessionUrl;
|
||||
console.log(`Message sent to existing session for PR #${pr.number}`);
|
||||
} else {
|
||||
console.log(`Creating new Devin session for PR #${pr.number}`);
|
||||
|
||||
const response = await fetch('https://api.devin.ai/v1/sessions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.DEVIN_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: conflictResolutionInstructions,
|
||||
title: `Resolve Conflicts: PR #${pr.number}`,
|
||||
tags: ['conflict-resolution', `pr-${pr.number}`]
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Devin API error for PR #${pr.number}: ${response.status} ${response.statusText}`);
|
||||
throw new Error(`Failed to create Devin session: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
sessionUrl = data.url || data.session_url;
|
||||
isNewSession = true;
|
||||
}
|
||||
|
||||
if (sessionUrl) {
|
||||
if (isNewSession) {
|
||||
console.log(`Devin session created for PR #${pr.number}: ${sessionUrl}`);
|
||||
}
|
||||
|
||||
const sessionStatusMessage = isNewSession
|
||||
? 'A Devin session has been created to automatically resolve them.'
|
||||
: 'The existing Devin session has been notified to resolve them.';
|
||||
|
||||
const commentBody = `### Devin AI is resolving merge conflicts
|
||||
|
||||
This PR has merge conflicts with the \`${pr.base_ref}\` branch. ${sessionStatusMessage}
|
||||
|
||||
[View Devin Session](${sessionUrl})
|
||||
|
||||
Devin will:
|
||||
1. Merge the latest \`${pr.base_ref}\` into this branch
|
||||
2. Resolve any conflicts intelligently
|
||||
3. Run lint/type checks to ensure validity
|
||||
4. Push the resolved changes
|
||||
|
||||
If you prefer to resolve conflicts manually, you can close the Devin session and handle it yourself.`;
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
console.log(`Posted comment to PR #${pr.number}`);
|
||||
} else {
|
||||
throw new Error(`Failed to get session URL for PR #${pr.number}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error handling Devin session for PR #${pr.number}: ${error.message}`);
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
@@ -12,7 +12,9 @@ jobs:
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- name: Cache Docs build
|
||||
uses: actions/cache@v4
|
||||
id: cache-docs-build
|
||||
|
||||
@@ -74,7 +74,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/cache-db
|
||||
- name: Build platform packages with Turbo
|
||||
|
||||
@@ -81,7 +81,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/yarn-playwright-install
|
||||
- run: yarn prisma generate
|
||||
|
||||
@@ -29,7 +29,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/yarn-playwright-install
|
||||
- name: Run E2E Atoms Tests
|
||||
|
||||
@@ -73,7 +73,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/yarn-playwright-install
|
||||
- run: yarn prisma generate
|
||||
|
||||
@@ -81,7 +81,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/yarn-playwright-install
|
||||
- run: yarn prisma generate
|
||||
|
||||
@@ -82,7 +82,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- uses: ./.github/actions/yarn-playwright-install
|
||||
- run: yarn prisma generate
|
||||
|
||||
@@ -79,7 +79,9 @@ jobs:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- run: yarn prisma generate
|
||||
- uses: ./.github/actions/cache-db
|
||||
|
||||
@@ -10,7 +10,9 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Run Lint
|
||||
run: yarn lint
|
||||
|
||||
@@ -53,7 +53,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- run: yarn prisma generate
|
||||
- uses: ./.github/actions/cache-build
|
||||
@@ -68,3 +70,9 @@ jobs:
|
||||
with:
|
||||
name: bundle
|
||||
path: apps/web/.next/analyze/__bundle_analysis.json
|
||||
|
||||
- name: Delete cached build
|
||||
uses: useblacksmith/cache-delete@v1
|
||||
with:
|
||||
key: prod-build-main-
|
||||
prefix: "true"
|
||||
|
||||
@@ -97,6 +97,15 @@ jobs:
|
||||
// NOTE: We use workflow run created_at instead of commit timestamp because
|
||||
// git commit timestamps can be arbitrarily backdated by attackers
|
||||
if (pr.labels?.some(l => l.name === 'run-ci')) {
|
||||
// Skip stale check if this is a re-run (run_attempt > 1)
|
||||
// Re-runs are explicitly triggered by maintainers
|
||||
const runAttempt = parseInt(process.env.GITHUB_RUN_ATTEMPT || '1', 10);
|
||||
if (runAttempt > 1) {
|
||||
console.log(`Re-run detected (attempt ${runAttempt}), trusting existing 'run-ci' label`);
|
||||
core.setOutput('is-trusted', true);
|
||||
return;
|
||||
}
|
||||
|
||||
const events = await github.paginate(github.rest.issues.listEvents, {
|
||||
owner,
|
||||
repo,
|
||||
@@ -165,7 +174,9 @@ jobs:
|
||||
db-cache-hit: ${{ steps.cache-db-check.outputs.cache-hit }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- name: Generate DB cache key
|
||||
id: cache-db-key
|
||||
uses: ./.github/actions/cache-db-key
|
||||
|
||||
@@ -43,7 +43,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- run: yarn prisma generate
|
||||
- name: Generate cache key
|
||||
|
||||
@@ -17,31 +17,78 @@ on:
|
||||
# in case manual trigger is needed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
BUILD_FROM_BRANCH:
|
||||
description: "Build from the selected branch instead of a tag"
|
||||
type: boolean
|
||||
default: false
|
||||
RELEASE_TAG:
|
||||
description: "Tag to build (e.g., v6.0.5)"
|
||||
required: true
|
||||
description: "Tag to build (e.g., v6.0.5) - required if BUILD_FROM_BRANCH is false"
|
||||
required: false
|
||||
PUSH_IMAGE:
|
||||
description: "Push the Docker image to DockerHub"
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: "Prepare Release"
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
outputs:
|
||||
release_tag: ${{ steps.determine-tag.outputs.release_tag }}
|
||||
checkout_ref: ${{ steps.determine-tag.outputs.checkout_ref }}
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.BUILD_FROM_BRANCH == false && inputs.RELEASE_TAG == ''
|
||||
run: |
|
||||
echo "::error::RELEASE_TAG is required when BUILD_FROM_BRANCH is false"
|
||||
exit 1
|
||||
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ (github.event_name == 'workflow_dispatch' && inputs.BUILD_FROM_BRANCH) && github.ref || inputs.RELEASE_TAG || github.ref }}
|
||||
|
||||
- name: "Determine tag and ref"
|
||||
id: determine-tag
|
||||
run: |
|
||||
# Determine checkout ref
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ "${{ inputs.BUILD_FROM_BRANCH }}" = "true" ]; then
|
||||
echo "checkout_ref=${{ github.ref }}" >> $GITHUB_OUTPUT
|
||||
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "checkout_ref=${{ inputs.RELEASE_TAG }}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "checkout_ref=${{ github.ref }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# Determine release tag
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
if [ "${{ inputs.BUILD_FROM_BRANCH }}" = "true" ]; then
|
||||
# Extract branch name and short SHA for the tag
|
||||
BRANCH_NAME="${GITHUB_REF#refs/heads/}"
|
||||
# Sanitize branch name: replace all invalid Docker tag characters with dashes
|
||||
# Docker tags can only contain lowercase/uppercase letters, digits, underscores, periods, and dashes
|
||||
# Also remove any leading dashes or periods
|
||||
SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9_.-]/-/g' | sed 's/^[-.]*//')
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
echo "release_tag=${SANITIZED_BRANCH}-${SHORT_SHA}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release_tag=${{ inputs.RELEASE_TAG }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "release_tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
release-amd64:
|
||||
name: "Release AMD64"
|
||||
needs: prepare
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.RELEASE_TAG || github.ref }}
|
||||
|
||||
- name: "Determine tag"
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "RELEASE_TAG=${{ inputs.RELEASE_TAG }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
fi
|
||||
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
||||
|
||||
- name: Build and test Docker image
|
||||
uses: ./.github/actions/docker-build-and-test
|
||||
@@ -82,20 +129,15 @@ jobs:
|
||||
|
||||
release-arm:
|
||||
name: "Release ARM"
|
||||
needs: prepare
|
||||
runs-on: ubuntu-24.04-arm
|
||||
env:
|
||||
RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.RELEASE_TAG || github.ref }}
|
||||
|
||||
- name: "Determine tag"
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
echo "RELEASE_TAG=${{ inputs.RELEASE_TAG }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "RELEASE_TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
fi
|
||||
ref: ${{ needs.prepare.outputs.checkout_ref }}
|
||||
|
||||
- name: Build and test Docker image
|
||||
uses: ./.github/actions/docker-build-and-test
|
||||
|
||||
@@ -47,7 +47,9 @@ jobs:
|
||||
- 5432:5432
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
if: inputs.DB_CACHE_HIT != 'true'
|
||||
- run: yarn prisma generate
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
name: Stale Community PR Devin Completion
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'PR number to complete'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
complete-stale-pr:
|
||||
name: Trigger Devin to Complete Stale Community PR
|
||||
# For labeled events: only run if 'stale' label was added
|
||||
# For workflow_dispatch: always run (we'll validate the PR in the steps)
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.event.label.name == 'stale'
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Get PR details
|
||||
id: pr
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
INPUT_PR_NUMBER: ${{ inputs.pr_number }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
let prNumber;
|
||||
let pr;
|
||||
|
||||
if (context.eventName === 'workflow_dispatch') {
|
||||
const inputPrNumber = process.env.INPUT_PR_NUMBER;
|
||||
prNumber = parseInt(inputPrNumber, 10);
|
||||
if (isNaN(prNumber) || prNumber <= 0) {
|
||||
core.setFailed(`Invalid PR number provided: "${inputPrNumber}". Please enter a valid PR number.`);
|
||||
return;
|
||||
}
|
||||
const { data } = await github.rest.pulls.get({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: prNumber
|
||||
});
|
||||
pr = data;
|
||||
} else {
|
||||
pr = context.payload.pull_request;
|
||||
prNumber = pr.number;
|
||||
}
|
||||
|
||||
if (!pr.head.repo) {
|
||||
core.setFailed('Cannot complete this PR: the source repository (fork) has been deleted.');
|
||||
return;
|
||||
}
|
||||
|
||||
const isFork = pr.head.repo.fork || pr.head.repo.full_name !== pr.base.repo.full_name;
|
||||
const maintainerCanModify = pr.maintainer_can_modify;
|
||||
const canProceed = !isFork || maintainerCanModify;
|
||||
const needsMaintainerAccess = isFork && !maintainerCanModify;
|
||||
|
||||
const headRepoFullName = pr.head.repo.full_name;
|
||||
const headRepoCloneUrl = pr.head.repo.clone_url;
|
||||
|
||||
core.setOutput('pr_number', prNumber);
|
||||
core.setOutput('pr_title', pr.title);
|
||||
core.setOutput('pr_author', pr.user.login);
|
||||
core.setOutput('pr_branch', pr.head.ref);
|
||||
core.setOutput('is_fork', isFork);
|
||||
core.setOutput('head_repo_full_name', headRepoFullName);
|
||||
core.setOutput('head_repo_clone_url', headRepoCloneUrl);
|
||||
core.setOutput('maintainer_can_modify', maintainerCanModify);
|
||||
core.setOutput('can_proceed', canProceed);
|
||||
core.setOutput('needs_maintainer_access', needsMaintainerAccess);
|
||||
|
||||
console.log(`PR #${prNumber}: "${pr.title}"`);
|
||||
console.log(`Author: ${pr.user.login}`);
|
||||
console.log(`Branch: ${pr.head.ref}`);
|
||||
console.log(`Is fork: ${isFork}`);
|
||||
console.log(`Head repo: ${headRepoFullName}`);
|
||||
console.log(`Maintainer can modify: ${maintainerCanModify}`);
|
||||
console.log(`Needs maintainer access: ${needsMaintainerAccess}`);
|
||||
|
||||
- name: Request maintainer access for fork PR
|
||||
if: steps.pr.outputs.needs_maintainer_access == 'true'
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const prNumber = parseInt(process.env.PR_NUMBER);
|
||||
const prAuthor = process.env.PR_AUTHOR;
|
||||
const { owner, repo } = context.repo;
|
||||
|
||||
const comments = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber
|
||||
});
|
||||
|
||||
const alreadyRequested = comments.data.some(comment =>
|
||||
comment.body?.includes('### Maintainer Access Needed')
|
||||
);
|
||||
|
||||
if (!alreadyRequested) {
|
||||
const commentBody = [
|
||||
'### Maintainer Access Needed',
|
||||
'',
|
||||
`Hi @${prAuthor}! Thanks for your contribution to Cal.com.`,
|
||||
'',
|
||||
`We'd like to help complete this stale PR, but we noticed that "Allow edits from maintainers" isn't enabled. We need this setting to push updates to your PR branch.`,
|
||||
'',
|
||||
'**Could you please enable this setting?** Here\'s how:',
|
||||
'1. Scroll down to the bottom of this PR page',
|
||||
'2. In the right sidebar, check the box that says **"Allow edits and access to secrets by maintainers"**',
|
||||
'',
|
||||
'This allows us to push commits directly to your PR branch, which helps us:',
|
||||
'- Complete any remaining work on your PR',
|
||||
'- Fix merge conflicts and make small adjustments',
|
||||
'- Get your contribution merged faster',
|
||||
'',
|
||||
'Once you\'ve enabled this setting, we\'ll be able to help finish up this PR. If you have any concerns about enabling this setting, feel free to let us know!'
|
||||
].join('\n');
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: prNumber,
|
||||
body: commentBody
|
||||
});
|
||||
|
||||
console.log(`Posted maintainer access request comment on PR #${prNumber}`);
|
||||
} else {
|
||||
console.log(`Maintainer access already requested on PR #${prNumber}`);
|
||||
}
|
||||
|
||||
core.setFailed(`Cannot complete this fork PR: the fork owner has not enabled "Allow edits from maintainers". A comment has been posted requesting access.`);
|
||||
|
||||
- name: Create Devin session
|
||||
if: steps.pr.outputs.can_proceed == 'true'
|
||||
env:
|
||||
DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
PR_TITLE: ${{ steps.pr.outputs.pr_title }}
|
||||
PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
|
||||
PR_BRANCH: ${{ steps.pr.outputs.pr_branch }}
|
||||
IS_FORK: ${{ steps.pr.outputs.is_fork }}
|
||||
HEAD_REPO_FULL_NAME: ${{ steps.pr.outputs.head_repo_full_name }}
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
run: |
|
||||
if [ "$IS_FORK" = "true" ]; then
|
||||
CLONE_INSTRUCTIONS="This is a fork PR. Clone from the fork repository: ${HEAD_REPO_FULL_NAME}
|
||||
|
||||
To check out this PR:
|
||||
1. Clone the fork: git clone https://github.com/${HEAD_REPO_FULL_NAME}.git
|
||||
2. Check out the branch: git checkout ${PR_BRANCH}
|
||||
3. Add the upstream remote: git remote add upstream https://github.com/${REPO_NAME}.git
|
||||
|
||||
IMPORTANT - Pushing to Fork PRs:
|
||||
Since this is a fork PR, you need to use the DEVIN_ACTIONS_PAT secret for authentication when pushing. Configure git to use this PAT for authentication before pushing your changes to the fork."
|
||||
else
|
||||
CLONE_INSTRUCTIONS="Clone the repository ${REPO_NAME} and check out the branch: ${PR_BRANCH}"
|
||||
fi
|
||||
|
||||
FULL_PROMPT="You are completing a stale community PR #${PR_NUMBER} in repository ${REPO_NAME}.
|
||||
|
||||
This PR was started by @${PR_AUTHOR} but has become stale. Your job is to complete it.
|
||||
|
||||
PR Title: ${PR_TITLE}
|
||||
PR Branch: ${PR_BRANCH}
|
||||
|
||||
${CLONE_INSTRUCTIONS}
|
||||
|
||||
Your tasks:
|
||||
1. Clone the repository as described above.
|
||||
2. Check out the PR branch: ${PR_BRANCH}
|
||||
3. Review the current state of the PR and understand what it's trying to accomplish.
|
||||
4. Read the PR description and any comments/review feedback on the PR.
|
||||
5. Complete any unfinished work on the PR:
|
||||
- Fix any issues mentioned in review comments
|
||||
- Ensure the code follows the repository's coding standards
|
||||
- Add any missing tests if applicable
|
||||
- Fix any linting or type errors
|
||||
6. Run the appropriate checks (lint, type-check, tests) to ensure the PR is ready for review.
|
||||
7. Commit your changes with clear commit messages.
|
||||
8. Push your changes to the PR branch.
|
||||
9. Post a summary comment on the PR explaining what you completed.
|
||||
10. Remove the 'stale' label from the PR if you've completed the work.
|
||||
11. Mark the PR as ready for review if applicable.
|
||||
|
||||
Rules and Guidelines:
|
||||
1. Respect the original author's intent and approach - don't rewrite the PR from scratch.
|
||||
2. Make minimal, focused changes that complete the PR's original goal.
|
||||
3. Follow the existing code style and conventions in the repository.
|
||||
4. If the PR's original approach seems fundamentally flawed, explain why in a comment instead of making major changes.
|
||||
5. Never ask for user confirmation. Never wait for user messages.
|
||||
6. Credit the original author in your commit messages where appropriate.
|
||||
7. CRITICAL: If this is a fork PR and you encounter ANY error when pushing (permission denied, authentication failure, etc.) even after using the DEVIN_ACTIONS_PAT, you MUST fail the task immediately. Do NOT attempt to push to a new branch in the main repository as a workaround. Simply report the error and stop."
|
||||
|
||||
RESPONSE=$(curl -s -X POST "https://api.devin.ai/v1/sessions" \
|
||||
-H "Authorization: Bearer ${DEVIN_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n \
|
||||
--arg prompt "$FULL_PROMPT" \
|
||||
--arg title "Complete Stale PR #${PR_NUMBER}: ${PR_TITLE}" \
|
||||
'{
|
||||
prompt: $prompt,
|
||||
title: $title,
|
||||
tags: ["stale-pr-completion", "pr-'${PR_NUMBER}'"]
|
||||
}')")
|
||||
|
||||
SESSION_URL=$(echo "$RESPONSE" | jq -r '.url // .session_url // empty')
|
||||
if [ -n "$SESSION_URL" ]; then
|
||||
echo "Devin session created: $SESSION_URL"
|
||||
echo "SESSION_URL=$SESSION_URL" >> $GITHUB_ENV
|
||||
else
|
||||
echo "Failed to create Devin session"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Post comment with Devin session link
|
||||
if: env.SESSION_URL != ''
|
||||
uses: actions/github-script@v7
|
||||
env:
|
||||
PR_AUTHOR: ${{ steps.pr.outputs.pr_author }}
|
||||
PR_NUMBER: ${{ steps.pr.outputs.pr_number }}
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const sessionUrl = process.env.SESSION_URL;
|
||||
const prAuthor = process.env.PR_AUTHOR;
|
||||
const prNumber = process.env.PR_NUMBER;
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: parseInt(prNumber),
|
||||
body: `### Devin AI is completing this stale PR\n\nThis PR by @${prAuthor} has been marked as stale. A Devin session has been created to complete the remaining work.\n\n[View Devin Session](${sessionUrl})\n\n---\n*Devin will review the PR, address any feedback, and push updates to complete this PR.*`
|
||||
});
|
||||
@@ -12,7 +12,9 @@ jobs:
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: ./.github/actions/dangerous-git-checkout
|
||||
with:
|
||||
sparse-checkout: .github
|
||||
- uses: ./.github/actions/cache-checkout
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- run: yarn prisma generate
|
||||
- run: yarn test -- --no-isolate
|
||||
|
||||
@@ -192,6 +192,23 @@ import { User } from "@prisma/client";
|
||||
import { Button } from "@calcom/ui";
|
||||
```
|
||||
|
||||
### API v2 Imports (apps/api/v2)
|
||||
|
||||
When importing from `@calcom/features` or `@calcom/trpc` into `apps/api/v2`, **do not import directly** because the API v2 app's `tsconfig.json` doesn't have path mappings for these modules, which causes "module not found" errors.
|
||||
|
||||
Instead, re-export from `packages/platform/libraries/index.ts` and import from `@calcom/platform-libraries`:
|
||||
|
||||
```typescript
|
||||
// Step 1: In packages/platform/libraries/index.ts, add the export
|
||||
export { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
|
||||
|
||||
// Step 2: In apps/api/v2, import from platform-libraries
|
||||
import { ProfileRepository } from "@calcom/platform-libraries";
|
||||
|
||||
// Bad - Direct import causes module not found error in apps/api/v2
|
||||
import { ProfileRepository } from "@calcom/features/profile/repositories/ProfileRepository";
|
||||
```
|
||||
|
||||
## PR Checklist
|
||||
|
||||
- [ ] Title follows conventional commits: `feat(scope): description`
|
||||
|
||||
@@ -39,7 +39,6 @@ COPY .yarn ./.yarn
|
||||
COPY apps/web ./apps/web
|
||||
COPY apps/api/v2 ./apps/api/v2
|
||||
COPY packages ./packages
|
||||
COPY tests ./tests
|
||||
|
||||
RUN yarn config set httpTimeout 1200000
|
||||
RUN npx turbo prune --scope=@calcom/web --scope=@calcom/trpc --docker
|
||||
|
||||
@@ -148,7 +148,7 @@ Here is what you need to be able to run Cal.com.
|
||||
|
||||
- Duplicate `.env.example` to `.env`
|
||||
- Use `openssl rand -base64 32` to generate a key and add it under `NEXTAUTH_SECRET` in the `.env` file.
|
||||
- Use `openssl rand -base64 32` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||
- Use `openssl rand -base64 24` to generate a key and add it under `CALENDSO_ENCRYPTION_KEY` in the `.env` file.
|
||||
|
||||
5. Setup Node
|
||||
If your Node version does not meet the project's requirements as instructed by the docs, "nvm" (Node Version Manager) allows using Node at the version required by the project:
|
||||
@@ -255,9 +255,9 @@ for Logger level to be set at info, for example.
|
||||
- [Setup postgres DB with Northflank](https://northflank.com/guides/deploy-postgres-database-on-northflank)
|
||||
- [Setup postgres DB with render](https://render.com/docs/databases)
|
||||
|
||||
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||
2. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
|
||||
|
||||
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
|
||||
3. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
|
||||
|
||||
In a development environment, run:
|
||||
|
||||
@@ -271,7 +271,7 @@ for Logger level to be set at info, for example.
|
||||
yarn workspace @calcom/prisma db-deploy
|
||||
```
|
||||
|
||||
1. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
||||
4. Run [mailhog](https://github.com/mailhog/MailHog) to view emails sent during development
|
||||
|
||||
> **_NOTE:_** Required when `E2E_TEST_MAILHOG_ENABLED` is "1"
|
||||
|
||||
@@ -280,7 +280,7 @@ for Logger level to be set at info, for example.
|
||||
docker run -d -p 8025:8025 -p 1025:1025 mailhog/mailhog
|
||||
```
|
||||
|
||||
1. Run (in development mode)
|
||||
5. Run (in development mode)
|
||||
|
||||
```sh
|
||||
yarn dev
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
# Dependency Injection (DI) Pattern
|
||||
|
||||
We use a Dependency Injection pattern powered by `@evyweb/ioctopus` to manage service and repository dependencies. This pattern ensures proper dependency management, testability, and consistent instantiation of services throughout the codebase.
|
||||
|
||||
We use the **moduleLoader pattern** which provides type-safe dependency injection. This ensures that if a service adds a new dependency, TypeScript will catch missing dependencies at build time rather than runtime.
|
||||
|
||||
## Core Concepts
|
||||
|
||||
The DI system consists of four main components:
|
||||
|
||||
**Tokens**: Unique symbols that identify each service or repository in the DI container. Every injectable class needs a corresponding token. Tokens should be defined in a `tokens.ts` file within the feature's `di/` directory.
|
||||
|
||||
**Modules** (`.module.ts` files): Define how classes are instantiated and what dependencies they require using the `bindModuleToClassOnToken` function. Each module exports a `moduleLoader` object that knows how to load itself and its dependencies.
|
||||
|
||||
**Containers** (`.container.ts` files): Create a container instance and expose getter functions that consumers use to obtain service instances. Containers use the moduleLoader to automatically load all required dependencies.
|
||||
|
||||
**`bindModuleToClassOnToken`**: A type-safe function that binds a class to a token and declares its dependencies. TypeScript ensures the declared dependencies match what the class constructor expects.
|
||||
|
||||
## How It Works
|
||||
|
||||
Here's the complete flow for creating a DI-enabled service:
|
||||
|
||||
**Step 1: Create tokens in the feature's di directory**
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/di/tokens.ts
|
||||
export const MY_FEATURE_DI_TOKENS = {
|
||||
MY_SERVICE: Symbol("MyService"),
|
||||
MY_SERVICE_MODULE: Symbol("MyServiceModule"),
|
||||
};
|
||||
```
|
||||
|
||||
Then import these tokens in the central tokens file:
|
||||
|
||||
```typescript
|
||||
// packages/features/di/tokens.ts
|
||||
import { MY_FEATURE_DI_TOKENS } from "@calcom/features/myfeature/di/tokens";
|
||||
|
||||
export const DI_TOKENS = {
|
||||
// ...existing tokens
|
||||
...MY_FEATURE_DI_TOKENS,
|
||||
};
|
||||
```
|
||||
|
||||
**Step 2: Define the service class with constructor injection**
|
||||
|
||||
For services with multiple dependencies, use a dependencies interface:
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/services/MyService.ts
|
||||
export interface IMyServiceDeps {
|
||||
bookingRepo: BookingRepository;
|
||||
userRepo: UserRepository;
|
||||
}
|
||||
|
||||
export class MyService {
|
||||
constructor(private deps: IMyServiceDeps) {}
|
||||
|
||||
async doSomething() {
|
||||
const bookings = await this.deps.bookingRepo.findMany({...});
|
||||
const user = await this.deps.userRepo.findById({...});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For services/repositories with a single dependency, pass it directly:
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/repositories/MyRepository.ts
|
||||
export class MyRepository {
|
||||
constructor(private prismaClient: PrismaClient) {}
|
||||
|
||||
async findById(id: string) {
|
||||
return this.prismaClient.myModel.findUnique({ where: { id } });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 3: Create a module file with moduleLoader**
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/di/MyService.module.ts
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { MyService } from "@calcom/features/myfeature/services/MyService";
|
||||
|
||||
import { moduleLoader as bookingRepositoryModuleLoader } from "./BookingRepository.module";
|
||||
import { moduleLoader as userRepositoryModuleLoader } from "./UserRepository.module";
|
||||
import { MY_FEATURE_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = MY_FEATURE_DI_TOKENS.MY_SERVICE;
|
||||
const moduleToken = MY_FEATURE_DI_TOKENS.MY_SERVICE_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: MyService,
|
||||
// For multiple dependencies, use depsMap:
|
||||
depsMap: {
|
||||
bookingRepo: bookingRepositoryModuleLoader,
|
||||
userRepo: userRepositoryModuleLoader,
|
||||
},
|
||||
});
|
||||
|
||||
export const moduleLoader: ModuleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
};
|
||||
|
||||
export type { MyService };
|
||||
```
|
||||
|
||||
For a single dependency, use `dep` instead of `depsMap`:
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/di/MyRepository.module.ts
|
||||
import { bindModuleToClassOnToken, createModule, type ModuleLoader } from "@calcom/features/di/di";
|
||||
import { MyRepository } from "@calcom/features/myfeature/repositories/MyRepository";
|
||||
|
||||
import { moduleLoader as prismaModuleLoader } from "@calcom/features/di/modules/Prisma";
|
||||
import { MY_FEATURE_DI_TOKENS } from "./tokens";
|
||||
|
||||
const thisModule = createModule();
|
||||
const token = MY_FEATURE_DI_TOKENS.MY_REPOSITORY;
|
||||
const moduleToken = MY_FEATURE_DI_TOKENS.MY_REPOSITORY_MODULE;
|
||||
|
||||
const loadModule = bindModuleToClassOnToken({
|
||||
module: thisModule,
|
||||
moduleToken,
|
||||
token,
|
||||
classs: MyRepository,
|
||||
// For single dependency, use dep:
|
||||
dep: prismaModuleLoader,
|
||||
});
|
||||
|
||||
export const moduleLoader: ModuleLoader = {
|
||||
token,
|
||||
loadModule,
|
||||
};
|
||||
|
||||
export type { MyRepository };
|
||||
```
|
||||
|
||||
**Step 4: Create a container that uses the moduleLoader**
|
||||
|
||||
```typescript
|
||||
// packages/features/myfeature/di/MyService.container.ts
|
||||
import { createContainer } from "@calcom/features/di/di";
|
||||
|
||||
import { type MyService, moduleLoader as myServiceModuleLoader } from "./MyService.module";
|
||||
|
||||
const myServiceContainer = createContainer();
|
||||
|
||||
export function getMyService(): MyService {
|
||||
myServiceModuleLoader.loadModule(myServiceContainer);
|
||||
return myServiceContainer.get<MyService>(myServiceModuleLoader.token);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 5: Use the service via the container's getter function**
|
||||
|
||||
```typescript
|
||||
// Anywhere in the codebase
|
||||
import { getMyService } from "@calcom/features/myfeature/di/MyService.container";
|
||||
|
||||
const myService = getMyService();
|
||||
await myService.doSomething();
|
||||
```
|
||||
|
||||
## Why moduleLoader Is Type-Safe
|
||||
|
||||
The `bindModuleToClassOnToken` function in `packages/features/di/di.ts` uses TypeScript generics to infer the required dependencies from the class constructor:
|
||||
|
||||
```typescript
|
||||
depsMap: Record<
|
||||
keyof (TClass extends new (deps: infer TDeps) => any ? TDeps : never),
|
||||
ModuleLoader
|
||||
>
|
||||
```
|
||||
|
||||
This means if `MyService` adds a new constructor dependency (e.g., `eventTypeRepo`), TypeScript will error because `depsMap` is missing that key. The error happens at build time, not runtime.
|
||||
|
||||
The `loadModule` function also automatically loads all dependencies recursively, so you don't need to manually track the dependency chain.
|
||||
|
||||
## Common Mistakes to Avoid
|
||||
|
||||
**Mistake 1: Creating a repository or service class with all static methods**
|
||||
|
||||
Static methods bypass the DI system entirely, making the code harder to test and breaking the dependency chain.
|
||||
|
||||
```typescript
|
||||
// Bad - Static methods bypass DI
|
||||
export class BookingRepository {
|
||||
static async findById(id: string) {
|
||||
return prisma.booking.findUnique({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
// Usage (wrong - no DI)
|
||||
const booking = await BookingRepository.findById("123");
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Good - Instance methods with constructor injection
|
||||
export class BookingRepository {
|
||||
constructor(private prismaClient: PrismaClient) {}
|
||||
|
||||
async findById(id: string) {
|
||||
return this.prismaClient.booking.findUnique({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
||||
// Usage (correct - via DI container)
|
||||
const bookingRepo = getBookingRepository();
|
||||
const booking = await bookingRepo.findById("123");
|
||||
```
|
||||
|
||||
**Mistake 2: Manually instantiating a class instead of using the DI container**
|
||||
|
||||
Even if you define a class with constructor injection, manually calling `new` bypasses the DI system and its benefits.
|
||||
|
||||
```typescript
|
||||
// Bad - Manual instantiation bypasses DI
|
||||
import { MyService } from "@calcom/features/myfeature/services/MyService";
|
||||
import { BookingRepository } from "@calcom/features/bookings/repositories/BookingRepository";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
const bookingRepo = new BookingRepository(prisma);
|
||||
const myService = new MyService({ bookingRepo });
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Good - Use the DI container's getter function
|
||||
import { getMyService } from "@calcom/features/myfeature/di/MyService.container";
|
||||
|
||||
const myService = getMyService();
|
||||
```
|
||||
|
||||
**Mistake 3: Importing Prisma directly in a service instead of using repository injection**
|
||||
|
||||
Services should depend on repositories, not directly on Prisma. This maintains proper separation of concerns.
|
||||
|
||||
```typescript
|
||||
// Bad - Service imports Prisma directly
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
export class MyService {
|
||||
async doSomething() {
|
||||
const bookings = await prisma.booking.findMany({...}); // Wrong!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Good - Service depends on repository via DI
|
||||
export interface IMyServiceDeps {
|
||||
bookingRepo: BookingRepository;
|
||||
}
|
||||
|
||||
export class MyService {
|
||||
constructor(private deps: IMyServiceDeps) {}
|
||||
|
||||
async doSomething() {
|
||||
const bookings = await this.deps.bookingRepo.findMany({...});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Mistake 4: Manual module loading in containers (not type-safe)**
|
||||
|
||||
Don't manually call `container.load()` for each dependency. This is not type-safe and can lead to runtime errors if you forget to load a dependency.
|
||||
|
||||
```typescript
|
||||
// Bad - Manual module loading is not type-safe
|
||||
const container = createContainer();
|
||||
container.load(DI_TOKENS.PRISMA_MODULE, prismaModule);
|
||||
container.load(DI_TOKENS.BOOKING_REPOSITORY_MODULE, bookingRepositoryModule);
|
||||
container.load(DI_TOKENS.MY_SERVICE_MODULE, myServiceModule);
|
||||
// If MyService adds a new dependency, TypeScript won't catch the missing load()
|
||||
|
||||
export function getMyService() {
|
||||
return container.get<MyService>(DI_TOKENS.MY_SERVICE);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// Good - Use moduleLoader for type-safe dependency loading
|
||||
import { moduleLoader as myServiceModuleLoader } from "./MyService.module";
|
||||
|
||||
const container = createContainer();
|
||||
|
||||
export function getMyService(): MyService {
|
||||
myServiceModuleLoader.loadModule(container); // Automatically loads all dependencies
|
||||
return container.get<MyService>(myServiceModuleLoader.token);
|
||||
}
|
||||
```
|
||||
|
||||
## Why Use DI?
|
||||
|
||||
- **Build-time safety**: TypeScript catches missing dependencies before runtime when using the moduleLoader pattern
|
||||
- **Testability**: Dependencies can be easily mocked in tests by providing alternative implementations
|
||||
- **Consistency**: All instances are created the same way with proper dependencies
|
||||
- **Maintainability**: Changing a dependency only requires updating the module binding, not every usage site
|
||||
- **Automatic dependency resolution**: The moduleLoader automatically loads all dependencies recursively
|
||||
- **Self-documenting**: Each module declares its own dependencies, making the dependency graph explicit
|
||||
@@ -13,7 +13,8 @@ Development
|
||||
- Install dependencies: `yarn`
|
||||
- Set up environment:
|
||||
- Copy `.env.example` to `.env`
|
||||
- Generate keys with `openssl rand -base64 32` for both `NEXTAUTH_SECRET` and `CALENDSO_ENCRYPTION_KEY`
|
||||
- Generate NEXTAUTH_SECRET with `openssl rand -base64 32`
|
||||
- Generate CALENDSO_ENCRYPTION_KEY with `openssl rand -base64 24` (must be 32 characters for AES256)
|
||||
- Configure Postgres database URL in `.env`
|
||||
- Set DATABASE_DIRECT_URL to the same value as DATABASE_URL
|
||||
|
||||
@@ -730,4 +731,8 @@ When doing logic that depends on Browser locale, use i18n.language (prefer to de
|
||||
|
||||
Note that with Date, you’re dealing with System time, so it’s not suited to everywhere (such as in the Booker, where instead we’ll likely migrate to Temporal) - but in most cases the above are suitable.
|
||||
|
||||
The main reason for doing so is that Dayjs uses a useful, but highly risky plugin system, which has led us to create `@calcom/dayjs` - this is heavy however, because it pre-loads ALL plugins, including locale handling. It’s a non-ideal solution to a problem that unfortunately exists due to Dayjs.
|
||||
The main reason for doing so is that Dayjs uses a useful, but highly risky plugin system, which has led us to create `@calcom/dayjs` - this is heavy however, because it pre-loads ALL plugins, including locale handling. It's a non-ideal solution to a problem that unfortunately exists due to Dayjs.
|
||||
|
||||
## Dependency Injection (DI) Pattern
|
||||
|
||||
See [di-pattern.md](./di-pattern.md) for the complete guide on using the type-safe moduleLoader pattern for dependency injection.
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { createMocks } from "node-mocks-http";
|
||||
import { describe, expect, test, beforeAll, afterAll } from "vitest";
|
||||
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { User, Team, EventType, Schedule } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import handler from "../../../../pages/api/event-types/[id]/_get";
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
describe("GET /api/event-types/[id]", () => {
|
||||
let testUser: User;
|
||||
let adminUser: User;
|
||||
let teamUser: User;
|
||||
let testTeam: Team;
|
||||
let testSchedule: Schedule;
|
||||
let complexEventType: EventType;
|
||||
let seatsEventType: EventType;
|
||||
let maxSeatsEventType: EventType;
|
||||
let nullSeatsEventType: EventType;
|
||||
let customFieldsEventType: EventType;
|
||||
let teamEventType: EventType;
|
||||
let metadataEventType: EventType;
|
||||
|
||||
beforeAll(async () => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
const createTestUser = prisma.user.create({
|
||||
data: {
|
||||
username: `test-user-${timestamp}`,
|
||||
name: "Test User",
|
||||
email: `test-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
const createAdminUser = prisma.user.create({
|
||||
data: {
|
||||
username: `admin-user-${timestamp}`,
|
||||
name: "Admin User",
|
||||
email: `admin-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
const createTeamUser = prisma.user.create({
|
||||
data: {
|
||||
username: `team-user-${timestamp}`,
|
||||
name: "Team User",
|
||||
email: `team-user-${timestamp}@example.com`,
|
||||
},
|
||||
});
|
||||
|
||||
[testUser, adminUser, teamUser] = await Promise.all([createTestUser, createAdminUser, createTeamUser]);
|
||||
|
||||
// Create a schedule for the test user (required for event types with scheduleId)
|
||||
testSchedule = await prisma.schedule.create({
|
||||
data: {
|
||||
name: `Test Schedule ${timestamp}`,
|
||||
userId: testUser.id,
|
||||
timeZone: "UTC",
|
||||
},
|
||||
});
|
||||
|
||||
testTeam = await prisma.team.create({
|
||||
data: {
|
||||
name: `Test Team ${timestamp}`,
|
||||
slug: `test-team-${timestamp}`,
|
||||
members: {
|
||||
createMany: {
|
||||
data: [
|
||||
{
|
||||
userId: teamUser.id,
|
||||
role: MembershipRole.MEMBER,
|
||||
accepted: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createComplexEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Complex Event Type",
|
||||
slug: `complex-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
locations: [
|
||||
{
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
{
|
||||
type: "attendeeInPerson",
|
||||
address: "123 Main St, City, State 12345",
|
||||
displayLocationPublicly: false,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
hostPhoneNumber: "+1234567890",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
editable: "system-but-optional",
|
||||
defaultLabel: "email_address",
|
||||
},
|
||||
{
|
||||
name: "customField",
|
||||
type: "text",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Custom Question",
|
||||
placeholder: "Enter your answer",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const createSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Seats Event Type",
|
||||
slug: `seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: 10,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
},
|
||||
});
|
||||
|
||||
const createMaxSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Max Seats Event Type",
|
||||
slug: `max-seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: 1000,
|
||||
seatsShowAttendees: false,
|
||||
seatsShowAvailabilityCount: true,
|
||||
},
|
||||
});
|
||||
|
||||
const createNullSeatsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Null Seats Event Type",
|
||||
slug: `null-seats-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
seatsPerTimeSlot: null,
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
},
|
||||
});
|
||||
|
||||
const createCustomFieldsEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Custom Fields Event Type",
|
||||
slug: `custom-fields-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
variant: "fullName",
|
||||
},
|
||||
{
|
||||
name: "customSelect",
|
||||
type: "select",
|
||||
required: true,
|
||||
editable: "user",
|
||||
label: "Preferred Time",
|
||||
options: [
|
||||
{ label: "Morning", value: "morning" },
|
||||
{ label: "Afternoon", value: "afternoon" },
|
||||
{ label: "Evening", value: "evening" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "customTextarea",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Additional Information",
|
||||
placeholder: "Please provide any additional details",
|
||||
maxLength: 500,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const createTeamEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Team Event Type",
|
||||
slug: `team-event-${timestamp}`,
|
||||
length: 60,
|
||||
teamId: testTeam.id,
|
||||
schedulingType: "COLLECTIVE",
|
||||
assignAllTeamMembers: true,
|
||||
},
|
||||
});
|
||||
|
||||
const createMetadataEventType = prisma.eventType.create({
|
||||
data: {
|
||||
title: "Metadata Event Type",
|
||||
slug: `metadata-event-${timestamp}`,
|
||||
length: 60,
|
||||
userId: testUser.id,
|
||||
scheduleId: testSchedule.id,
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
disableStandardEmails: false,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 30,
|
||||
unit: "minutes",
|
||||
},
|
||||
apps: {
|
||||
stripe: {
|
||||
price: 2500,
|
||||
currency: "usd",
|
||||
},
|
||||
},
|
||||
},
|
||||
recurringEvent: {
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
},
|
||||
bookingLimits: {
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
},
|
||||
durationLimits: {
|
||||
PER_DAY: 480,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
[
|
||||
complexEventType,
|
||||
seatsEventType,
|
||||
maxSeatsEventType,
|
||||
nullSeatsEventType,
|
||||
customFieldsEventType,
|
||||
teamEventType,
|
||||
metadataEventType,
|
||||
] = await Promise.all([
|
||||
createComplexEventType,
|
||||
createSeatsEventType,
|
||||
createMaxSeatsEventType,
|
||||
createNullSeatsEventType,
|
||||
createCustomFieldsEventType,
|
||||
createTeamEventType,
|
||||
createMetadataEventType,
|
||||
]);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.eventType.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: [
|
||||
complexEventType.id,
|
||||
seatsEventType.id,
|
||||
maxSeatsEventType.id,
|
||||
nullSeatsEventType.id,
|
||||
customFieldsEventType.id,
|
||||
teamEventType.id,
|
||||
metadataEventType.id,
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.team.delete({
|
||||
where: { id: testTeam.id },
|
||||
});
|
||||
|
||||
await prisma.schedule.delete({
|
||||
where: { id: testSchedule.id },
|
||||
});
|
||||
|
||||
await prisma.user.deleteMany({
|
||||
where: {
|
||||
id: {
|
||||
in: [testUser.id, adminUser.id, teamUser.id],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("Errors", () => {
|
||||
test("Returns 403 if user not admin/team member/event owner", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = adminUser.id;
|
||||
|
||||
await expect(handler(req)).rejects.toThrowError(
|
||||
expect.objectContaining({
|
||||
statusCode: 403,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Success", async () => {
|
||||
test("Returns event type if user is admin", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = true;
|
||||
req.userId = adminUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(complexEventType.id);
|
||||
});
|
||||
|
||||
test("Returns event type if user is in team associated with event type", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: teamEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = teamUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(teamEventType.id);
|
||||
});
|
||||
|
||||
test("Returns event type if user is the event type owner", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.id).toEqual(complexEventType.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Data Validation", () => {
|
||||
test("Returns properly validated event type with complex locations", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: complexEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type).toBeDefined();
|
||||
expect(responseData.event_type.id).toBe(complexEventType.id);
|
||||
expect(responseData.event_type.locations).toHaveLength(3);
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
|
||||
const locations = responseData.event_type.locations as Array<Record<string, unknown>>;
|
||||
expect(locations[0]).toMatchObject({
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
});
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<Record<string, unknown>>;
|
||||
expect(bookingFields[0]).toMatchObject({
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
});
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with seats configuration", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: seatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(10);
|
||||
expect(responseData.event_type.seatsShowAttendees).toBe(true);
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBe(false);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with maximum seats", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: maxSeatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(1000);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with null seats", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: nullSeatsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAttendees).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBeNull();
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with custom inputs and booking fields", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: customFieldsEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<{
|
||||
name: string;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
maxLength?: number;
|
||||
}>;
|
||||
const selectField = bookingFields.find((f) => f.name === "customSelect");
|
||||
expect(selectField?.options).toHaveLength(3);
|
||||
expect(selectField?.options?.[0]).toMatchObject({
|
||||
label: "Morning",
|
||||
value: "morning",
|
||||
});
|
||||
|
||||
const textareaField = bookingFields.find((f) => f.name === "customTextarea");
|
||||
expect(textareaField?.maxLength).toBe(500);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with team configuration", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: teamEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = teamUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.teamId).toBe(testTeam.id);
|
||||
expect(responseData.event_type.schedulingType).toBe("COLLECTIVE");
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with complex metadata", async () => {
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: metadataEventType.id,
|
||||
},
|
||||
});
|
||||
|
||||
req.userId = testUser.id;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
expect(responseData.event_type.recurringEvent).toMatchObject({
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
});
|
||||
expect(responseData.event_type.bookingLimits).toMatchObject({
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
});
|
||||
expect(responseData.event_type.durationLimits).toMatchObject({
|
||||
PER_DAY: 480,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -165,4 +165,353 @@ describe("GET /api/event-types/[id]", () => {
|
||||
expect(prismaMock.team.findFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Data Validation", () => {
|
||||
test("Returns properly validated event type with complex locations", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const complexEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
locations: [
|
||||
{
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
{
|
||||
type: "attendeeInPerson",
|
||||
address: "123 Main St, City, State 12345",
|
||||
displayLocationPublicly: false,
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
hostPhoneNumber: "+1234567890",
|
||||
},
|
||||
],
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
type: "email",
|
||||
required: true,
|
||||
editable: "system-but-optional",
|
||||
defaultLabel: "email_address",
|
||||
},
|
||||
{
|
||||
name: "customField",
|
||||
type: "text",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Custom Question",
|
||||
placeholder: "Enter your answer",
|
||||
},
|
||||
],
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(complexEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type).toBeDefined();
|
||||
expect(responseData.event_type.id).toBe(eventTypeId);
|
||||
expect(responseData.event_type.locations).toHaveLength(3);
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
|
||||
const locations = responseData.event_type.locations as Array<Record<string, unknown>>;
|
||||
expect(locations[0]).toMatchObject({
|
||||
type: "integrations:zoom",
|
||||
link: "https://zoom.us/j/123456789",
|
||||
});
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<Record<string, unknown>>;
|
||||
expect(bookingFields[0]).toMatchObject({
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
});
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with seats configuration", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const seatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: 10,
|
||||
seatsShowAttendees: true,
|
||||
seatsShowAvailabilityCount: false,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(seatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(10);
|
||||
expect(responseData.event_type.seatsShowAttendees).toBe(true);
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBe(false);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with maximum seats", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const maxSeatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: 1000,
|
||||
seatsShowAttendees: false,
|
||||
seatsShowAvailabilityCount: true,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(maxSeatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBe(1000);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with null seats", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const nullSeatsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
seatsPerTimeSlot: null,
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(nullSeatsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.seatsPerTimeSlot).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAttendees).toBeNull();
|
||||
expect(responseData.event_type.seatsShowAvailabilityCount).toBeNull();
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with custom inputs and booking fields", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const customFieldsEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
bookingFields: [
|
||||
{
|
||||
name: "name",
|
||||
type: "name",
|
||||
required: true,
|
||||
editable: "system",
|
||||
defaultLabel: "your_name",
|
||||
variant: "fullName",
|
||||
},
|
||||
{
|
||||
name: "customSelect",
|
||||
type: "select",
|
||||
required: true,
|
||||
editable: "user",
|
||||
label: "Preferred Time",
|
||||
options: [
|
||||
{ label: "Morning", value: "morning" },
|
||||
{ label: "Afternoon", value: "afternoon" },
|
||||
{ label: "Evening", value: "evening" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "customTextarea",
|
||||
type: "textarea",
|
||||
required: false,
|
||||
editable: "user",
|
||||
label: "Additional Information",
|
||||
placeholder: "Please provide any additional details",
|
||||
maxLength: 500,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(customFieldsEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.bookingFields).toHaveLength(3);
|
||||
|
||||
const bookingFields = responseData.event_type.bookingFields as Array<{
|
||||
name: string;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
maxLength?: number;
|
||||
}>;
|
||||
const selectField = bookingFields.find((f) => f.name === "customSelect");
|
||||
expect(selectField?.options).toHaveLength(3);
|
||||
expect(selectField?.options?.[0]).toMatchObject({
|
||||
label: "Morning",
|
||||
value: "morning",
|
||||
});
|
||||
|
||||
const textareaField = bookingFields.find((f) => f.name === "customTextarea");
|
||||
expect(textareaField?.maxLength).toBe(500);
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with team configuration", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const teamId = 9999;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const teamEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
teamId,
|
||||
userId: null,
|
||||
schedulingType: "COLLECTIVE",
|
||||
assignAllTeamMembers: true,
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(teamEventType);
|
||||
prismaMock.team.findFirst.mockResolvedValue({
|
||||
id: teamId,
|
||||
// @ts-expect-error requires mockDeep which will be introduced in the Prisma 6.7.0 upgrade, ignore for now.
|
||||
members: [
|
||||
{
|
||||
userId,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
req.isSystemWideAdmin = false;
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.teamId).toBe(teamId);
|
||||
expect(responseData.event_type.schedulingType).toBe("COLLECTIVE");
|
||||
});
|
||||
|
||||
test("Returns properly validated event type with complex metadata", async () => {
|
||||
const eventTypeId = 123456;
|
||||
const userId = 333333;
|
||||
const { req } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||
method: "GET",
|
||||
body: {},
|
||||
query: {
|
||||
id: eventTypeId,
|
||||
},
|
||||
});
|
||||
|
||||
const metadataEventType = buildEventType({
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
scheduleId: 1111,
|
||||
metadata: {
|
||||
additionalNotesRequired: true,
|
||||
disableStandardEmails: false,
|
||||
requiresConfirmationThreshold: {
|
||||
time: 30,
|
||||
unit: "minutes",
|
||||
},
|
||||
apps: {
|
||||
stripe: {
|
||||
price: 2500,
|
||||
currency: "usd",
|
||||
},
|
||||
},
|
||||
},
|
||||
recurringEvent: {
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
},
|
||||
bookingLimits: {
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
},
|
||||
durationLimits: {
|
||||
PER_DAY: 480,
|
||||
},
|
||||
});
|
||||
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(metadataEventType);
|
||||
|
||||
req.userId = userId;
|
||||
const responseData = await handler(req);
|
||||
|
||||
expect(responseData.event_type.metadata).toBeDefined();
|
||||
expect(responseData.event_type.recurringEvent).toMatchObject({
|
||||
freq: 2,
|
||||
count: 12,
|
||||
interval: 1,
|
||||
});
|
||||
expect(responseData.event_type.bookingLimits).toMatchObject({
|
||||
PER_DAY: 3,
|
||||
PER_WEEK: 10,
|
||||
});
|
||||
expect(responseData.event_type.durationLimits).toMatchObject({
|
||||
PER_DAY: 480,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,12 +12,13 @@ ENV NODE_ENV="production"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=8192"
|
||||
ENV DATABASE_DIRECT_URL=${DATABASE_DIRECT_URL}
|
||||
ENV DATABASE_URL=${DATABASE_URL}
|
||||
ENV USE_POOL="true"
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN yarn install
|
||||
RUN yarn workspace @calcom/api-v2 run generate-schemas
|
||||
RUN yarn workspace @calcom/platform-libraries run build
|
||||
RUN yarn workspace @calcom/api-v2 run build:docker
|
||||
RUN yarn workspace @calcom/api-v2 run build
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"license": "UNLICENSED",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn dev:build && nest build",
|
||||
"build": "nest build",
|
||||
"build:docker": "yarn workspace @calcom/platform-constants build && yarn workspace @calcom/platform-enums build && yarn workspace @calcom/platform-utils build && yarn workspace @calcom/platform-types build && yarn workspace @calcom/platform-libraries build && yarn workspace @calcom/trpc build:server",
|
||||
"format": "biome format --write src test",
|
||||
"start": "nest start",
|
||||
"dev:build:watch": "concurrently --names \"libraries,lru-fix,constants,enums,utils,types\" \"yarn _dev:build:watch:libraries\" \"yarn _dev:build:watch:libraries:lru-fix\" \"yarn _dev:build:watch:constants\" \"yarn _dev:build:watch:enums\" \"yarn _dev:build:watch:utils\" \"yarn _dev:build:watch:types\"",
|
||||
@@ -47,7 +48,7 @@
|
||||
"@microsoft/microsoft-graph-types-beta": "0.42.0-preview",
|
||||
"@nest-lab/throttler-storage-redis": "1.0.0",
|
||||
"@nestjs/axios": "4.0.0",
|
||||
"@nestjs/bull": "10.1.1",
|
||||
"@nestjs/bull": "11.0.4",
|
||||
"@nestjs/common": "10.4.20",
|
||||
"@nestjs/config": "3.2.0",
|
||||
"@nestjs/core": "10.4.20",
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import { AppModule } from "@/app.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { CustomThrottlerGuard } from "@/lib/throttler-guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import type { PlatformOAuthClient, RateLimit, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { TestingModule } from "@nestjs/testing";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -16,9 +11,12 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { RateLimitRepositoryFixture } from "test/fixtures/repository/rate-limit.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import type { User, PlatformOAuthClient, Team, RateLimit } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { CustomThrottlerGuard } from "@/lib/throttler-guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("AppController", () => {
|
||||
describe("Rate limiting", () => {
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import "./instrument";
|
||||
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
|
||||
import type { ValidationError } from "@nestjs/common";
|
||||
import { BadRequestException, ValidationPipe, VersioningType } from "@nestjs/common";
|
||||
import { BaseExceptionFilter, HttpAdapterHost } from "@nestjs/core";
|
||||
import type { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import * as cookieParser from "cookie-parser";
|
||||
import { Request } from "express";
|
||||
import helmet from "helmet";
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
VERSION_2024_04_15,
|
||||
API_VERSIONS_ENUM,
|
||||
CAL_API_VERSION_HEADER,
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
} from "@calcom/platform-constants";
|
||||
|
||||
import { CalendarServiceExceptionFilter } from "./filters/calendar-service-exception.filter";
|
||||
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
||||
|
||||
export const bootstrap = (app: NestExpressApplication): NestExpressApplication => {
|
||||
app.enableShutdownHooks();
|
||||
|
||||
app.enableVersioning({
|
||||
type: VersioningType.CUSTOM,
|
||||
extractor: (request: unknown) => {
|
||||
const headerVersion = (request as Request)?.headers[CAL_API_VERSION_HEADER] as string | undefined;
|
||||
if (headerVersion && API_VERSIONS.includes(headerVersion as API_VERSIONS_ENUM)) {
|
||||
return headerVersion;
|
||||
}
|
||||
return VERSION_2024_04_15;
|
||||
},
|
||||
defaultVersion: VERSION_2024_04_15,
|
||||
});
|
||||
|
||||
app.use(helmet());
|
||||
|
||||
app.enableCors({
|
||||
origin: "*",
|
||||
methods: ["GET", "PATCH", "DELETE", "HEAD", "POST", "PUT", "OPTIONS"],
|
||||
allowedHeaders: [
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
CAL_API_VERSION_HEADER,
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"Origin",
|
||||
],
|
||||
maxAge: 86_400,
|
||||
});
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
validationError: {
|
||||
target: true,
|
||||
value: true,
|
||||
},
|
||||
exceptionFactory(errors: ValidationError[]) {
|
||||
return new BadRequestException({ errors });
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// Exception filters, new filters go at the bottom, keep the order
|
||||
const { httpAdapter } = app.get(HttpAdapterHost);
|
||||
app.useGlobalFilters(new PrismaExceptionFilter());
|
||||
app.useGlobalFilters(new ZodExceptionFilter());
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalFilters(new TRPCExceptionFilter());
|
||||
app.useGlobalFilters(new CalendarServiceExceptionFilter());
|
||||
|
||||
app.use(cookieParser());
|
||||
|
||||
if (process?.env?.API_GLOBAL_PREFIX) {
|
||||
app.setGlobalPrefix(process?.env?.API_GLOBAL_PREFIX);
|
||||
}
|
||||
|
||||
return app;
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import "./instrument";
|
||||
|
||||
import {
|
||||
API_VERSIONS,
|
||||
API_VERSIONS_ENUM,
|
||||
CAL_API_VERSION_HEADER,
|
||||
VERSION_2024_04_15,
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
X_CAL_SECRET_KEY,
|
||||
} from "@calcom/platform-constants";
|
||||
import type { ValidationError } from "@nestjs/common";
|
||||
import { BadRequestException, Logger, ValidationPipe, VersioningType } from "@nestjs/common";
|
||||
import type { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import cookieParser from "cookie-parser";
|
||||
import { Request } from "express";
|
||||
import helmet from "helmet";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
|
||||
import { CalendarServiceExceptionFilter } from "./filters/calendar-service-exception.filter";
|
||||
import { TRPCExceptionFilter } from "./filters/trpc-exception.filter";
|
||||
|
||||
const logger: Logger = new Logger("Bootstrap");
|
||||
|
||||
export const bootstrap = (app: NestExpressApplication): NestExpressApplication => {
|
||||
try {
|
||||
if (!process.env.VERCEL) {
|
||||
app.enableShutdownHooks();
|
||||
}
|
||||
app.enableVersioning({
|
||||
type: VersioningType.CUSTOM,
|
||||
extractor: (request: unknown) => {
|
||||
const headerVersion = (request as Request)?.headers[CAL_API_VERSION_HEADER] as string | undefined;
|
||||
if (headerVersion && API_VERSIONS.includes(headerVersion as API_VERSIONS_ENUM)) {
|
||||
return headerVersion;
|
||||
}
|
||||
return VERSION_2024_04_15;
|
||||
},
|
||||
defaultVersion: VERSION_2024_04_15,
|
||||
});
|
||||
app.use(helmet());
|
||||
app.enableCors({
|
||||
origin: "*",
|
||||
methods: ["GET", "PATCH", "DELETE", "HEAD", "POST", "PUT", "OPTIONS"],
|
||||
allowedHeaders: [
|
||||
X_CAL_CLIENT_ID,
|
||||
X_CAL_SECRET_KEY,
|
||||
X_CAL_PLATFORM_EMBED,
|
||||
CAL_API_VERSION_HEADER,
|
||||
"Accept",
|
||||
"Authorization",
|
||||
"Content-Type",
|
||||
"Origin",
|
||||
],
|
||||
maxAge: 86_400,
|
||||
});
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
validationError: {
|
||||
target: true,
|
||||
value: true,
|
||||
},
|
||||
exceptionFactory(errors: ValidationError[]): BadRequestException {
|
||||
return new BadRequestException({ errors });
|
||||
},
|
||||
})
|
||||
);
|
||||
// Exception filters, new filters go at the bottom, keep the order
|
||||
app.useGlobalFilters(new PrismaExceptionFilter());
|
||||
app.useGlobalFilters(new ZodExceptionFilter());
|
||||
app.useGlobalFilters(new HttpExceptionFilter());
|
||||
app.useGlobalFilters(new TRPCExceptionFilter());
|
||||
app.useGlobalFilters(new CalendarServiceExceptionFilter());
|
||||
app.use(cookieParser());
|
||||
|
||||
if (process?.env?.API_GLOBAL_PREFIX) {
|
||||
app.setGlobalPrefix(process?.env?.API_GLOBAL_PREFIX);
|
||||
}
|
||||
|
||||
return app;
|
||||
} catch (error) {
|
||||
logger.error("Error starting NestJS app:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -1,20 +1,23 @@
|
||||
import { getEnv } from "@/env";
|
||||
|
||||
import type { AppConfig } from "./type";
|
||||
|
||||
const loadConfig = (): AppConfig => {
|
||||
const env = getEnv("NODE_ENV", "development");
|
||||
const apiPort = Number(getEnv("API_PORT", "5555"));
|
||||
const apiUrl = getEnv("API_URL", "http://localhost");
|
||||
let portSuffix = "";
|
||||
if (process.env.API_PORT && env === "development") {
|
||||
portSuffix = `:${apiPort}`;
|
||||
}
|
||||
|
||||
return {
|
||||
env: {
|
||||
type: getEnv("NODE_ENV", "development"),
|
||||
type: env,
|
||||
},
|
||||
api: {
|
||||
port: Number(getEnv("API_PORT", "5555")),
|
||||
path: getEnv("API_URL", "http://localhost"),
|
||||
url: `${getEnv("API_URL", "http://localhost")}${
|
||||
process.env.API_PORT && getEnv("NODE_ENV", "development") === "development"
|
||||
? `:${Number(getEnv("API_PORT", "5555"))}`
|
||||
: ""
|
||||
}/v2`,
|
||||
port: apiPort,
|
||||
path: apiUrl,
|
||||
url: `${apiUrl}${portSuffix}/v2`,
|
||||
keyPrefix: getEnv("API_KEY_PREFIX", "cal_"),
|
||||
licenseKey: getEnv("CALCOM_LICENSE_KEY", ""),
|
||||
licenseKeyUrl: getEnv("GET_LICENSE_KEY_URL", "https://console.cal.com/api/license"),
|
||||
@@ -27,6 +30,7 @@ const loadConfig = (): AppConfig => {
|
||||
workerReadPoolMax: getEnv("DATABASE_READ_WORKER_POOL_MAX", 4),
|
||||
workerWritePoolMax: getEnv("DATABASE_WRITE_WORKER_POOL_MAX", 6),
|
||||
redisUrl: getEnv("REDIS_URL"),
|
||||
usePool: getEnv("USE_POOL", "true") === "true",
|
||||
},
|
||||
next: {
|
||||
authSecret: getEnv("NEXTAUTH_SECRET"),
|
||||
@@ -40,7 +44,9 @@ const loadConfig = (): AppConfig => {
|
||||
app: {
|
||||
baseUrl: getEnv("WEB_APP_URL", "https://app.cal.com"),
|
||||
},
|
||||
e2e: getEnv("IS_E2E", "false") === "true" ? true : false,
|
||||
e2e: getEnv("IS_E2E", "false") === "true",
|
||||
enableSlotsWorkers: getEnv("ENABLE_SLOTS_WORKERS", "true") === "true",
|
||||
slotsWorkerPoolSize: Number(getEnv("SLOTS_WORKER_POOL_SIZE", "4")),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export type AppConfig = {
|
||||
workerReadPoolMax: number;
|
||||
workerWritePoolMax: number;
|
||||
redisUrl: string;
|
||||
usePool: boolean;
|
||||
};
|
||||
next: {
|
||||
authSecret: string;
|
||||
@@ -32,4 +33,6 @@ export type AppConfig = {
|
||||
baseUrl: string;
|
||||
};
|
||||
e2e: boolean;
|
||||
enableSlotsWorkers: boolean;
|
||||
slotsWorkerPoolSize: number;
|
||||
};
|
||||
|
||||
+16
-17
@@ -1,5 +1,20 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { BookingResponse } from "@calcom/platform-libraries";
|
||||
import { type RegularBookingCreateResult } from "@calcom/platform-libraries/bookings";
|
||||
import type { ApiErrorResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input";
|
||||
import { CreateRecurringBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-recurring-booking.input";
|
||||
import { GetBookingOutput_2024_04_15 } from "@/ee/bookings/2024-04-15/outputs/get-booking.output";
|
||||
@@ -10,22 +25,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { BookingResponse } from "@calcom/platform-libraries";
|
||||
import { type RegularBookingCreateResult } from "@calcom/platform-libraries/bookings";
|
||||
import type { ApiSuccessResponse, ApiErrorResponse } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
describe("Bookings Endpoints 2024-04-15", () => {
|
||||
describe("User Authenticated", () => {
|
||||
|
||||
@@ -349,6 +349,7 @@ export class BookingsController_2024_04_15 {
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
noEmail: bookingRequest.body.noEmail,
|
||||
},
|
||||
creationSource: "API_V2",
|
||||
});
|
||||
|
||||
createdBookings.forEach(async (booking) => {
|
||||
|
||||
+18
-19
@@ -1,5 +1,22 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingInput_2024_04_15 } from "@/ee/bookings/2024-04-15/inputs/create-booking.input";
|
||||
import {
|
||||
GetBookingsDataEntry,
|
||||
@@ -15,24 +32,6 @@ import {
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import {
|
||||
ApiSuccessResponse,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
EventTypeOutput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:4321";
|
||||
|
||||
|
||||
@@ -212,10 +212,9 @@ export class BookingsController_2024_08_13 {
|
||||
}
|
||||
|
||||
@Get("/:bookingUid/recordings")
|
||||
// @Pbac(["booking.readRecordings"])
|
||||
@Pbac(["booking.readRecordings"])
|
||||
@Permissions([BOOKING_READ])
|
||||
@UseGuards(BookingUidGuard)
|
||||
// @UseGuards(ApiAuthGuard, BookingUidGuard, BookingPbacGuard)
|
||||
@UseGuards(ApiAuthGuard, BookingUidGuard, BookingPbacGuard)
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@ApiOperation({
|
||||
summary: "Get all the recordings for the booking",
|
||||
@@ -230,16 +229,13 @@ export class BookingsController_2024_08_13 {
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: recordings,
|
||||
message:
|
||||
"This endpoint will require authentication in a future release. Please update your integration to include valid credentials. See https://cal.com/docs/api-reference/v2/introduction#authentication for details.",
|
||||
};
|
||||
}
|
||||
|
||||
@Get("/:bookingUid/transcripts")
|
||||
// @Pbac(["booking.readRecordings"])
|
||||
@Pbac(["booking.readRecordings"])
|
||||
@Permissions([BOOKING_READ])
|
||||
@UseGuards(BookingUidGuard)
|
||||
// @UseGuards(ApiAuthGuard, BookingUidGuard, BookingPbacGuard)
|
||||
@UseGuards(ApiAuthGuard, BookingUidGuard, BookingPbacGuard)
|
||||
@ApiHeader(API_KEY_OR_ACCESS_TOKEN_HEADER)
|
||||
@ApiOperation({
|
||||
summary: "Get Cal Video real time transcript download links for the booking",
|
||||
@@ -258,8 +254,6 @@ export class BookingsController_2024_08_13 {
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: transcripts ?? [],
|
||||
message:
|
||||
"This endpoint will require authentication in a future release. Please update your integration to include valid credentials. See https://cal.com/docs/api-reference/v2/introduction#authentication for details.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -280,7 +274,7 @@ export class BookingsController_2024_08_13 {
|
||||
const { bookings, pagination } = await this.bookingsService.getBookings(queryParams, {
|
||||
email: user.email,
|
||||
id: user.id,
|
||||
orgId: profile?.organizationId,
|
||||
orgId: profile?.organizationId ?? undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -397,18 +391,18 @@ export class BookingsController_2024_08_13 {
|
||||
<Note>Please make sure to pass in the cal-api-version header value as mentioned in the Headers section. Not passing the correct value will default to an older version of this endpoint.</Note>
|
||||
`,
|
||||
})
|
||||
async markNoShow(
|
||||
@Param("bookingUid") bookingUid: string,
|
||||
@Body() body: MarkAbsentBookingInput_2024_08_13,
|
||||
@GetUser() user: ApiAuthGuardUser
|
||||
): Promise<MarkAbsentBookingOutput_2024_08_13> {
|
||||
const booking = await this.bookingsService.markAbsent(bookingUid, user.id, body, user.uuid);
|
||||
async markNoShow(
|
||||
@Param("bookingUid") bookingUid: string,
|
||||
@Body() body: MarkAbsentBookingInput_2024_08_13,
|
||||
@GetUser() user: ApiAuthGuardUser
|
||||
): Promise<MarkAbsentBookingOutput_2024_08_13> {
|
||||
const booking = await this.bookingsService.markAbsent(bookingUid, user.id, body, user.uuid);
|
||||
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: booking,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: booking,
|
||||
};
|
||||
}
|
||||
|
||||
@Post("/:bookingUid/reassign")
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
||||
@@ -1,25 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { AddGuestsOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/add-guests.output";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
ERROR_STATUS,
|
||||
@@ -32,7 +10,28 @@ import {
|
||||
OrganizerAddGuestsEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import type { BookingOutput_2024_08_13, CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { AddGuestsOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/add-guests.output";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
const attendeeAddGuestsEmailSpy = jest
|
||||
.spyOn(AttendeeAddGuestsEmail.prototype, "getHtml")
|
||||
|
||||
+31
-31
@@ -1,5 +1,32 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
AttendeeCancelledEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
OrganizerScheduledEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import {
|
||||
BookingOutput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CancelBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/cancel-booking.output";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
@@ -9,34 +36,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerScheduledEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
AttendeeCancelledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
|
||||
jest.spyOn(AttendeeScheduledEmail.prototype as any, "getHtml").mockImplementation(async function () {
|
||||
return "<html><body>Mocked Email Content</body></html>";
|
||||
@@ -177,6 +176,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -216,7 +216,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.reschedulingReason).toEqual(body.reschedulingReason);
|
||||
expect(data.start).toEqual(body.start);
|
||||
@@ -262,7 +262,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
|
||||
+50
-51
@@ -1,16 +1,9 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CalVideoService } from "@/ee/bookings/2024-08-13/services/cal-video.service";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { Booking, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
@@ -19,9 +12,15 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { Booking, User, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CalVideoService } from "@/ee/bookings/2024-08-13/services/cal-video.service";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Booking access authorization", () => {
|
||||
@@ -186,51 +185,51 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// describe("GET /v2/bookings/:bookingUid/recordings - Authorization", () => {
|
||||
// it("should allow booking organizer to access recordings", async () => {
|
||||
// const calVideoService = app.get(CalVideoService);
|
||||
// jest.spyOn(calVideoService, "getRecordings").mockResolvedValue([]);
|
||||
describe("GET /v2/bookings/:bookingUid/recordings - Authorization", () => {
|
||||
it("should allow booking organizer to access recordings", async () => {
|
||||
const calVideoService = app.get(CalVideoService);
|
||||
jest.spyOn(calVideoService, "getRecordings").mockResolvedValue([]);
|
||||
|
||||
// const response = await request(app.getHttpServer())
|
||||
// .get(`/v2/bookings/${testBooking.uid}/recordings`)
|
||||
// .set("Authorization", `Bearer ${ownerApiKey}`)
|
||||
// .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
// .expect(200);
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${testBooking.uid}/recordings`)
|
||||
.set("Authorization", `Bearer ${ownerApiKey}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200);
|
||||
|
||||
// expect(response.body.status).toEqual(SUCCESS_STATUS);
|
||||
// });
|
||||
expect(response.body.status).toEqual(SUCCESS_STATUS);
|
||||
});
|
||||
|
||||
// it("should return 403 when unauthorized user tries to access recordings", async () => {
|
||||
// await request(app.getHttpServer())
|
||||
// .get(`/v2/bookings/${testBooking.uid}/recordings`)
|
||||
// .set("Authorization", `Bearer ${unauthorizedApiKey}`)
|
||||
// .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
// .expect(403);
|
||||
// });
|
||||
// });
|
||||
it("should return 403 when unauthorized user tries to access recordings", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${testBooking.uid}/recordings`)
|
||||
.set("Authorization", `Bearer ${unauthorizedApiKey}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
// describe("GET /v2/bookings/:bookingUid/transcripts - Authorization", () => {
|
||||
// it("should allow booking organizer to access transcripts", async () => {
|
||||
// const calVideoService = app.get(CalVideoService);
|
||||
// jest.spyOn(calVideoService, "getTranscripts").mockResolvedValue([]);
|
||||
describe("GET /v2/bookings/:bookingUid/transcripts - Authorization", () => {
|
||||
it("should allow booking organizer to access transcripts", async () => {
|
||||
const calVideoService = app.get(CalVideoService);
|
||||
jest.spyOn(calVideoService, "getTranscripts").mockResolvedValue([]);
|
||||
|
||||
// const response = await request(app.getHttpServer())
|
||||
// .get(`/v2/bookings/${testBooking.uid}/transcripts`)
|
||||
// .set("Authorization", `Bearer ${ownerApiKey}`)
|
||||
// .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
// .expect(200);
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${testBooking.uid}/transcripts`)
|
||||
.set("Authorization", `Bearer ${ownerApiKey}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(200);
|
||||
|
||||
// expect(response.body.status).toEqual(SUCCESS_STATUS);
|
||||
// });
|
||||
expect(response.body.status).toEqual(SUCCESS_STATUS);
|
||||
});
|
||||
|
||||
// it("should return 403 when unauthorized user tries to access transcripts", async () => {
|
||||
// await request(app.getHttpServer())
|
||||
// .get(`/v2/bookings/${testBooking.uid}/transcripts`)
|
||||
// .set("Authorization", `Bearer ${unauthorizedApiKey}`)
|
||||
// .set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
// .expect(403);
|
||||
// });
|
||||
// });
|
||||
it("should return 403 when unauthorized user tries to access transcripts", async () => {
|
||||
await request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${testBooking.uid}/transcripts`)
|
||||
.set("Authorization", `Bearer ${unauthorizedApiKey}`)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(403);
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await bookingsRepositoryFixture.deleteById(testBooking.id);
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
BookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Booking, EventType, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -18,11 +17,15 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import { CreateBookingInput_2024_08_13, GetBookingOutput_2024_08_13, GetSeatedBookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import { BookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { Booking, PlatformOAuthClient, Team, User, EventType } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Booking fields", () => {
|
||||
@@ -775,7 +778,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
if (responseDataIsBooking(responseBody.data)) {
|
||||
// eslint-disable-next-line
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: GetSeatedBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.attendees[0].name).toEqual(`${splitName.firstName} ${splitName.lastName}`);
|
||||
expect(data.attendees[0].bookingFieldsResponses.name).toEqual(splitName);
|
||||
@@ -1197,4 +1200,4 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
function responseDataIsBooking(data: any): data is BookingOutput_2024_08_13 {
|
||||
return !Array.isArray(data) && typeof data === "object" && data && "id" in data;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { Booking, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -17,9 +11,14 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { User, Booking, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Calendar Links", () => {
|
||||
|
||||
+12
-13
@@ -1,15 +1,10 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import { BookingOutput_2024_08_13, GetBookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { Booking, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -17,10 +12,14 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import { BookingOutput_2024_08_13, GetBookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { User, Booking, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Bookings confirmation", () => {
|
||||
|
||||
+31
-32
@@ -1,5 +1,35 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
AttendeeCancelledEmail,
|
||||
AttendeeDeclinedEmail,
|
||||
AttendeeRequestEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
OrganizerRequestEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
OrganizerScheduledEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
@@ -8,37 +38,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
OrganizerScheduledEmail,
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
AttendeeCancelledEmail,
|
||||
AttendeeRequestEmail,
|
||||
OrganizerRequestEmail,
|
||||
AttendeeDeclinedEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
|
||||
jest
|
||||
.spyOn(AttendeeScheduledEmail.prototype, "getHtml")
|
||||
|
||||
+24
-25
@@ -1,27 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
AttendeeCancelledEmail,
|
||||
@@ -39,7 +15,30 @@ import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
// Mock all email sending prototypes
|
||||
jest
|
||||
|
||||
+28
-29
@@ -1,5 +1,32 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
AttendeeCancelledEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
OrganizerScheduledEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CancelBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/cancel-booking.output";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
@@ -10,34 +37,6 @@ import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import {
|
||||
OrganizerScheduledEmail,
|
||||
AttendeeScheduledEmail,
|
||||
OrganizerRescheduledEmail,
|
||||
AttendeeRescheduledEmail,
|
||||
OrganizerCancelledEmail,
|
||||
AttendeeCancelledEmail,
|
||||
} from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
|
||||
jest
|
||||
.spyOn(AttendeeScheduledEmail.prototype, "getHtml")
|
||||
|
||||
+124
-40
@@ -1,29 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { Locales } from "@/lib/enums/locales";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import {
|
||||
CreateManagedUserData,
|
||||
CreateManagedUserOutput,
|
||||
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
SUCCESS_STATUS,
|
||||
@@ -40,6 +14,31 @@ import {
|
||||
GetBookingsOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { Locales } from "@/lib/enums/locales";
|
||||
import { MembershipsModule } from "@/modules/memberships/memberships.module";
|
||||
import {
|
||||
CreateManagedUserData,
|
||||
CreateManagedUserOutput,
|
||||
} from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/create-managed-user.output";
|
||||
import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-clients-users.service";
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:4321";
|
||||
|
||||
@@ -356,7 +355,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getFirstManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data;
|
||||
expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount);
|
||||
|
||||
@@ -369,7 +368,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getSecondManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount);
|
||||
@@ -408,7 +407,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getFirstManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data;
|
||||
expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount);
|
||||
|
||||
@@ -421,7 +420,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getSecondManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount);
|
||||
@@ -435,7 +434,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getThirdManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const thirdManagedUserBookings: BookingOutput_2024_08_13[] = thirdManagedUserBookingsResponseBody.data;
|
||||
expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount);
|
||||
});
|
||||
@@ -473,7 +472,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getFirstManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data;
|
||||
expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount);
|
||||
|
||||
@@ -486,7 +485,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getSecondManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount);
|
||||
@@ -525,7 +524,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const firstManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getFirstManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const firstManagedUserBookings: BookingOutput_2024_08_13[] = firstManagedUserBookingsResponseBody.data;
|
||||
expect(firstManagedUserBookings.length).toEqual(firstManagedUserBookingsCount);
|
||||
|
||||
@@ -538,7 +537,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const secondManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getSecondManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookings.length).toEqual(secondManagedUserBookingsCount);
|
||||
@@ -552,7 +551,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
const thirdManagedUserBookingsResponseBody: GetBookingsOutput_2024_08_13 =
|
||||
getThirdManagedUserBookings.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const thirdManagedUserBookings: BookingOutput_2024_08_13[] = thirdManagedUserBookingsResponseBody.data;
|
||||
expect(thirdManagedUserBookings.length).toEqual(thirdManagedUserBookingsCount);
|
||||
});
|
||||
@@ -568,7 +567,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
|
||||
const thirdManagedUserAttendeeBookingsResponseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const thirdManagedUserAttendeeBookings: BookingOutput_2024_08_13[] =
|
||||
thirdManagedUserAttendeeBookingsResponseBody.data;
|
||||
expect(thirdManagedUserBookingsCount).toEqual(2);
|
||||
@@ -584,7 +583,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
|
||||
const thirdManagedUserAttendeeBookingsResponseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const thirdManagedUserAttendeeBookings: BookingOutput_2024_08_13[] =
|
||||
thirdManagedUserAttendeeBookingsResponseBody.data;
|
||||
expect(thirdManagedUserBookingsCount).toEqual(2);
|
||||
@@ -600,7 +599,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
|
||||
const secondManagedUserAttendeeBookingsResponseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserAttendeeBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserAttendeeBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookingsCount).toEqual(4);
|
||||
@@ -616,7 +615,7 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
|
||||
const secondManagedUserAttendeeBookingsResponseBody: GetBookingsOutput_2024_08_13 = response.body;
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const secondManagedUserAttendeeBookings: BookingOutput_2024_08_13[] =
|
||||
secondManagedUserAttendeeBookingsResponseBody.data;
|
||||
expect(secondManagedUserBookingsCount).toEqual(4);
|
||||
@@ -624,6 +623,91 @@ describe("Managed user bookings 2024-08-13", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("displayEmail fields", () => {
|
||||
it("should return displayEmail without CUID suffix for host", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 7, 10, 0, 0)).toISOString(),
|
||||
eventTypeId: firstManagedUserEventTypeId,
|
||||
attendee: {
|
||||
name: "External Attendee",
|
||||
email: "external-display-email-test@example.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "en",
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const bookingData = response.body.data;
|
||||
|
||||
// Host email should have CUID, displayEmail should not
|
||||
expect(bookingData.hosts[0].email).toEqual(firstManagedUser.user.email);
|
||||
expect(bookingData.hosts[0].displayEmail).toEqual(firstManagedUserEmail);
|
||||
});
|
||||
|
||||
it("should return displayEmail without CUID suffix for attendee", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 7, 10, 30, 0)).toISOString(),
|
||||
eventTypeId: firstManagedUserEventTypeId,
|
||||
attendee: {
|
||||
name: secondManagedUser.user.name!,
|
||||
email: secondManagedUser.user.email,
|
||||
timeZone: secondManagedUser.user.timeZone,
|
||||
language: secondManagedUser.user.locale,
|
||||
},
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const bookingData = response.body.data;
|
||||
|
||||
// Attendee email should have CUID, displayEmail should not
|
||||
expect(bookingData.attendees[0].email).toEqual(secondManagedUser.user.email);
|
||||
expect(bookingData.attendees[0].displayEmail).toEqual(secondManagedUserEmail);
|
||||
|
||||
// bookingFieldsResponses should also have displayEmail
|
||||
expect(bookingData.bookingFieldsResponses.email).toEqual(secondManagedUser.user.email);
|
||||
expect(bookingData.bookingFieldsResponses.displayEmail).toEqual(secondManagedUserEmail);
|
||||
});
|
||||
|
||||
it("should return displayGuests without CUID suffix", async () => {
|
||||
const body: CreateBookingInput_2024_08_13 = {
|
||||
start: new Date(Date.UTC(2030, 0, 7, 11, 0, 0)).toISOString(),
|
||||
eventTypeId: firstManagedUserEventTypeId,
|
||||
attendee: {
|
||||
name: "External Attendee",
|
||||
email: "external-display-guests-test@example.com",
|
||||
timeZone: "Europe/Rome",
|
||||
language: "en",
|
||||
},
|
||||
guests: [secondManagedUser.user.email],
|
||||
location: "https://meet.google.com/abc-def-ghi",
|
||||
};
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.post("/v2/bookings")
|
||||
.send(body)
|
||||
.set(CAL_API_VERSION_HEADER, VERSION_2024_08_13)
|
||||
.expect(201);
|
||||
|
||||
const bookingData = response.body.data;
|
||||
|
||||
// guests should have CUID, displayGuests should not
|
||||
expect(bookingData.bookingFieldsResponses.guests).toContain(secondManagedUser.user.email);
|
||||
expect(bookingData.bookingFieldsResponses.displayGuests).toContain(secondManagedUserEmail);
|
||||
});
|
||||
});
|
||||
|
||||
describe("booking confirmation by org admin", () => {
|
||||
it("should allow org admin managed user to confirm booking using access token", async () => {
|
||||
const bookingRequiringConfirmation = await bookingsRepositoryFixture.create({
|
||||
|
||||
+18
-17
@@ -1,17 +1,10 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
||||
import { BOOKING_REASSIGN_PERMISSION_ERROR } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { Booking, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
@@ -23,10 +16,16 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type { CreateBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { Booking, User, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { ReassignBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reassign-booking.output";
|
||||
import { BOOKING_REASSIGN_PERMISSION_ERROR } from "@/ee/bookings/2024-08-13/services/bookings.service";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Reassign bookings", () => {
|
||||
@@ -492,6 +491,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
id: teamUser2.id,
|
||||
name: teamUser2.name,
|
||||
email: teamUser2.email,
|
||||
displayEmail: teamUser2.email,
|
||||
});
|
||||
|
||||
const reassigned = await bookingsRepositoryFixture.getByUid(roundRobinBooking.uid);
|
||||
@@ -532,6 +532,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
id: teamUser1.id,
|
||||
name: teamUser1.name,
|
||||
email: teamUser1.email,
|
||||
displayEmail: teamUser1.email,
|
||||
});
|
||||
|
||||
const reassigned = await bookingsRepositoryFixture.getByUid(roundRobinBooking.uid);
|
||||
@@ -664,7 +665,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
|
||||
expect(booking).toBeDefined();
|
||||
expect(booking?.userId).toBeDefined();
|
||||
const initialHostId = booking!.userId!;
|
||||
const initialHostId = booking?.userId!;
|
||||
rescheduleReasonBookingInitialHostId = initialHostId;
|
||||
|
||||
const expectedInitialTitle = `${teamRoundRobinWithRescheduleReasonEventTypeTitle} between ${
|
||||
@@ -706,7 +707,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(booking).toBeDefined();
|
||||
expect(booking?.userId).toBeDefined();
|
||||
|
||||
const currentHostId = booking!.userId!;
|
||||
const currentHostId = booking?.userId!;
|
||||
const initialHostId = rescheduleReasonBookingInitialHostId;
|
||||
const initialHostName = initialHostId === teamUser1.id ? teamUser1.name : teamUser2.name;
|
||||
|
||||
|
||||
+23
-19
@@ -1,16 +1,15 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import { AttendeeCancelledEmail, OrganizerCancelledEmail } from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
CancelBookingInput_2024_08_13,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -18,15 +17,15 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import { AttendeeCancelledEmail, OrganizerCancelledEmail } from "@calcom/platform-libraries/emails";
|
||||
import type {
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Creating recurring bookings", () => {
|
||||
@@ -185,6 +184,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -206,6 +206,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -262,6 +263,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -284,6 +286,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -304,6 +307,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(thirdBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
|
||||
+17
-18
@@ -1,5 +1,21 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { EventType, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
@@ -8,23 +24,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { EventType, User } from "@calcom/prisma/client";
|
||||
|
||||
describe("Reschedule bookings 2024-08-13", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
+33
-29
@@ -1,5 +1,29 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CancelBookingInput_2024_08_13,
|
||||
CancelSeatedBookingInput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
CreateSeatedBookingOutput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RescheduleSeatedBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { DateTime } from "luxon";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
@@ -8,31 +32,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { DateTime } from "luxon";
|
||||
import * as request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { BookingSeatRepositoryFixture } from "test/fixtures/repository/booking-seat.repository.fixture";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CancelBookingInput_2024_08_13,
|
||||
CancelSeatedBookingInput_2024_08_13,
|
||||
CreateSeatedBookingOutput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RescheduleSeatedBookingInput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Seated bookings", () => {
|
||||
@@ -201,6 +200,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -276,6 +276,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstAttendee).toEqual({
|
||||
name: createdSeatedBooking.attendees[0].name,
|
||||
email: createdSeatedBooking.attendees[0].email,
|
||||
displayEmail: createdSeatedBooking.attendees[0].displayEmail,
|
||||
timeZone: createdSeatedBooking.attendees[0].timeZone,
|
||||
language: createdSeatedBooking.attendees[0].language,
|
||||
absent: false,
|
||||
@@ -291,6 +292,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondAttendee).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -329,7 +331,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const expected = structuredClone(createdSeatedBooking);
|
||||
// note(Lauris): seatUid in get response resides only in each attendee object
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
delete expected.seatUid;
|
||||
expect(data).toEqual(expected);
|
||||
} else {
|
||||
@@ -356,7 +358,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
const seatedBookingExpected = structuredClone(createdSeatedBooking);
|
||||
// note(Lauris): seatUid in get response resides only in each attendee object
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
delete seatedBookingExpected.seatUid;
|
||||
expect(seatedBooking).toEqual(seatedBookingExpected);
|
||||
});
|
||||
@@ -418,6 +420,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: attendee?.name,
|
||||
email: attendee?.email,
|
||||
displayEmail: attendee?.displayEmail,
|
||||
timeZone: attendee?.timeZone,
|
||||
language: attendee?.language,
|
||||
absent: false,
|
||||
@@ -841,6 +844,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -920,7 +924,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
function responseDataIsCreateSeatedBooking(data: any): data is CreateSeatedBookingOutput_2024_08_13 {
|
||||
return data.hasOwnProperty("seatUid");
|
||||
return Object.hasOwn(data, "seatUid");
|
||||
}
|
||||
|
||||
function responseDataIsGetSeatedBooking(data: any): data is GetSeatedBookingOutput_2024_08_13 {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { HostsRepositoryFixture } from "test/fixtures/repository/hosts.repository.fixture";
|
||||
@@ -23,17 +23,16 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { RescheduleBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/reschedule-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("Team bookings", () => {
|
||||
@@ -519,6 +518,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -573,6 +573,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: "919876543210@sms.cal.com",
|
||||
displayEmail: "919876543210@sms.cal.com",
|
||||
phoneNumber: body.attendee.phoneNumber,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
@@ -629,6 +630,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -636,6 +638,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[1]).toEqual({
|
||||
name: teamUser2.name,
|
||||
email: teamUser2.email,
|
||||
displayEmail: teamUser2.email,
|
||||
timeZone: teamUser2.timeZone,
|
||||
language: teamUser2.locale,
|
||||
absent: false,
|
||||
@@ -690,6 +693,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -744,6 +748,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -927,6 +932,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: phoneBasedBooking.attendees[0].name,
|
||||
email: phoneBasedBooking.attendees[0].email,
|
||||
displayEmail: phoneBasedBooking.attendees[0].displayEmail,
|
||||
phoneNumber: phoneBasedBooking.attendees[0].phoneNumber,
|
||||
timeZone: phoneBasedBooking.attendees[0].timeZone,
|
||||
language: phoneBasedBooking.attendees[0].language,
|
||||
@@ -1010,6 +1016,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
|
||||
+20
-21
@@ -1,16 +1,17 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { UpdateBookingLocationOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/update-location.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
ERROR_STATUS,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_08_13,
|
||||
} from "@calcom/platform-constants";
|
||||
import type { BookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import { UpdateBookingLocationInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { Team } from "@calcom/prisma/client";
|
||||
import { Booking } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -18,17 +19,15 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
ERROR_STATUS,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_08_13,
|
||||
} from "@calcom/platform-constants";
|
||||
import { UpdateBookingLocationInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { BookingOutput_2024_08_13 } from "@calcom/platform-types";
|
||||
import { Booking } from "@calcom/prisma/client";
|
||||
import type { Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { UpdateBookingLocationOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/update-location.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
type TestUser = {
|
||||
id: number;
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_06_14,
|
||||
VERSION_2024_08_13,
|
||||
X_CAL_CLIENT_ID,
|
||||
} from "@calcom/platform-constants";
|
||||
import { EventManager } from "@calcom/platform-libraries/event-types";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
MarkAbsentBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import { FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE } from "@calcom/platform-types";
|
||||
import type { Booking, EventType, PlatformOAuthClient, Team, User, Workflow } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { advanceTo, clear } from "jest-date-mock";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WorkflowRepositoryFixture } from "test/fixtures/repository/workflow.repository.fixture";
|
||||
import { WorkflowReminderRepositoryFixture } from "test/fixtures/repository/workflow-reminder.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CancelBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/cancel-booking.output";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { MarkAbsentBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/mark-absent.output";
|
||||
@@ -12,44 +49,6 @@ import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import { advanceTo, clear } from "jest-date-mock";
|
||||
import * as request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WorkflowReminderRepositoryFixture } from "test/fixtures/repository/workflow-reminder.repository.fixture";
|
||||
import { WorkflowRepositoryFixture } from "test/fixtures/repository/workflow.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_06_14,
|
||||
VERSION_2024_08_13,
|
||||
X_CAL_CLIENT_ID,
|
||||
} from "@calcom/platform-constants";
|
||||
import { EventManager } from "@calcom/platform-libraries/event-types";
|
||||
import { FAILED_EVENT_TYPE_IDENTIFICATION_ERROR_MESSAGE } from "@calcom/platform-types";
|
||||
import type {
|
||||
CreateEventTypeInput_2024_06_14,
|
||||
GetBookingOutput_2024_08_13,
|
||||
GetBookingsOutput_2024_08_13,
|
||||
GetSeatedBookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
RescheduleBookingInput_2024_08_13,
|
||||
MarkAbsentBookingInput_2024_08_13,
|
||||
CancelBookingInput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { EventType, User, Workflow, Booking, PlatformOAuthClient, Team } from "@calcom/prisma/client";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("User bookings", () => {
|
||||
@@ -483,6 +482,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -493,8 +493,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.bookingFieldsResponses).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
...body.bookingFieldsResponses,
|
||||
guests: body.guests,
|
||||
displayGuests: body.guests,
|
||||
location: {
|
||||
optionValue: googleMeetUrl,
|
||||
value: "link",
|
||||
@@ -566,6 +568,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(firstBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -588,6 +591,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(secondBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -609,6 +613,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(thirdBooking.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -1518,7 +1523,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: RecurringBookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
@@ -1598,7 +1603,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: RecurringBookingOutput_2024_08_13 = responseBody.data;
|
||||
const booking = createdRecurringBooking[1];
|
||||
expect(data.absentHost).toEqual(true);
|
||||
@@ -1633,7 +1638,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: RecurringBookingOutput_2024_08_13 = responseBody.data;
|
||||
const booking = createdRecurringBooking[2];
|
||||
|
||||
@@ -1713,7 +1718,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(responseBody.status).toEqual(SUCCESS_STATUS);
|
||||
expect(responseBody.data).toBeDefined();
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const data: BookingOutput_2024_08_13 = responseBody.data;
|
||||
expect(data.id).toBeDefined();
|
||||
expect(data.uid).toBeDefined();
|
||||
@@ -2077,6 +2082,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
absent: false,
|
||||
@@ -2087,8 +2093,10 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.bookingFieldsResponses).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
...body.bookingFieldsResponses,
|
||||
guests: body.guests,
|
||||
displayGuests: body.guests,
|
||||
location: {
|
||||
optionValue: googleMeetUrl,
|
||||
value: "link",
|
||||
@@ -2527,6 +2535,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.attendees[0]).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
timeZone: body.attendee.timeZone,
|
||||
language: body.attendee.language,
|
||||
phoneNumber: body.attendee.phoneNumber,
|
||||
@@ -2535,6 +2544,7 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
expect(data.bookingFieldsResponses).toEqual({
|
||||
name: body.attendee.name,
|
||||
email: body.attendee.email,
|
||||
displayEmail: body.attendee.email,
|
||||
attendeePhoneNumber: body.attendee.phoneNumber,
|
||||
smsReminderNumber: body.attendee.phoneNumber,
|
||||
});
|
||||
@@ -3131,24 +3141,22 @@ describe("Bookings Endpoints 2024-08-13", () => {
|
||||
});
|
||||
|
||||
const calVideoService = app.get(CalVideoService);
|
||||
jest
|
||||
.spyOn(calVideoService, "getVideoSessions")
|
||||
.mockResolvedValue([
|
||||
{
|
||||
id: mockSessions[0].id,
|
||||
room: mockSessions[0].room,
|
||||
startTime: mockSessions[0].start_time,
|
||||
duration: mockSessions[0].duration,
|
||||
ongoing: mockSessions[0].ongoing,
|
||||
maxParticipants: mockSessions[0].max_participants,
|
||||
participants: mockSessions[0].participants.map((p) => ({
|
||||
userId: p.user_id,
|
||||
userName: p.user_name,
|
||||
joinTime: p.join_time,
|
||||
duration: p.duration,
|
||||
})),
|
||||
},
|
||||
]);
|
||||
jest.spyOn(calVideoService, "getVideoSessions").mockResolvedValue([
|
||||
{
|
||||
id: mockSessions[0].id,
|
||||
room: mockSessions[0].room,
|
||||
startTime: mockSessions[0].start_time,
|
||||
duration: mockSessions[0].duration,
|
||||
ongoing: mockSessions[0].ongoing,
|
||||
maxParticipants: mockSessions[0].max_participants,
|
||||
participants: mockSessions[0].participants.map((p) => ({
|
||||
userId: p.user_id,
|
||||
userName: p.user_name,
|
||||
joinTime: p.join_time,
|
||||
duration: p.duration,
|
||||
})),
|
||||
},
|
||||
]);
|
||||
|
||||
const response = await request(app.getHttpServer())
|
||||
.get(`/v2/bookings/${booking.uid}/conferencing-sessions`)
|
||||
|
||||
+18
-19
@@ -1,16 +1,15 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
BookingOutput_2024_08_13,
|
||||
CreateBookingInput_2024_08_13,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { EventType, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { BookingsRepositoryFixture } from "test/fixtures/repository/bookings.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -18,15 +17,15 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_08_13 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CreateBookingInput_2024_08_13,
|
||||
BookingOutput_2024_08_13,
|
||||
CreateRecurringBookingInput_2024_08_13,
|
||||
RecurringBookingOutput_2024_08_13,
|
||||
} from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, EventType, User } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateBookingOutput_2024_08_13 } from "@/ee/bookings/2024-08-13/outputs/create-booking.output";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { SchedulesService_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/services/schedules.service";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Bookings Endpoints 2024-08-13", () => {
|
||||
describe("User bookings", () => {
|
||||
|
||||
@@ -33,6 +33,7 @@ export class BookingGuestsService_2024_08_13 {
|
||||
ctx: { user },
|
||||
input: { bookingId: booking.id, guests: input.guests },
|
||||
emailsEnabled,
|
||||
actionSource: "API_V2",
|
||||
});
|
||||
if (res.message === "Guests added") {
|
||||
return await this.bookingsService.getBooking(bookingUid, user);
|
||||
|
||||
@@ -37,7 +37,6 @@ import { BadRequestException } from "@nestjs/common";
|
||||
import { Request } from "express";
|
||||
import { DateTime } from "luxon";
|
||||
import { z } from "zod";
|
||||
|
||||
export const BOOKING_REASSIGN_PERMISSION_ERROR = "You do not have permission to reassign this booking";
|
||||
|
||||
import {
|
||||
@@ -69,6 +68,7 @@ import {
|
||||
import type { RescheduleSeatedBookingInput_2024_08_13 } from "@calcom/platform-types";
|
||||
import type { PrismaClient } from "@calcom/prisma";
|
||||
import type { EventType, User, Team } from "@calcom/prisma/client";
|
||||
import { makeUserActor } from "@calcom/platform-libraries/bookings";
|
||||
|
||||
type CreatedBooking = {
|
||||
hosts: { id: number }[];
|
||||
@@ -115,7 +115,7 @@ export class BookingsService_2024_08_13 {
|
||||
private readonly recurringBookingService: RecurringBookingService,
|
||||
private readonly instantBookingCreateService: InstantBookingCreateService,
|
||||
private readonly eventTypeAccessService: EventTypeAccessService
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async createBooking(request: Request, body: CreateBookingInput, authUser: AuthOptionalUser) {
|
||||
let bookingTeamEventType = false;
|
||||
@@ -321,8 +321,7 @@ export class BookingsService_2024_08_13 {
|
||||
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
|
||||
if (!this.isValidSingleOptionValue(submittedValue, allowedOptionValues)) {
|
||||
throw new BadRequestException(
|
||||
`Invalid option '${submittedValue}' for booking field '${
|
||||
eventTypeBookingField.name
|
||||
`Invalid option '${submittedValue}' for booking field '${eventTypeBookingField.name
|
||||
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
|
||||
);
|
||||
}
|
||||
@@ -336,8 +335,7 @@ export class BookingsService_2024_08_13 {
|
||||
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
|
||||
if (!this.areValidMultipleOptionValues(submittedValues, allowedOptionValues)) {
|
||||
throw new BadRequestException(
|
||||
`One or more invalid options for booking field '${
|
||||
eventTypeBookingField.name
|
||||
`One or more invalid options for booking field '${eventTypeBookingField.name
|
||||
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
|
||||
);
|
||||
}
|
||||
@@ -351,8 +349,7 @@ export class BookingsService_2024_08_13 {
|
||||
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
|
||||
if (!this.areValidMultipleOptionValues(submittedValues, allowedOptionValues)) {
|
||||
throw new BadRequestException(
|
||||
`One or more invalid options for booking field '${
|
||||
eventTypeBookingField.name
|
||||
`One or more invalid options for booking field '${eventTypeBookingField.name
|
||||
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
|
||||
);
|
||||
}
|
||||
@@ -367,8 +364,7 @@ export class BookingsService_2024_08_13 {
|
||||
const allowedOptionValues = eventTypeBookingField.options.map((opt) => opt.value);
|
||||
if (!this.isValidSingleOptionValue(submittedValue, allowedOptionValues)) {
|
||||
throw new BadRequestException(
|
||||
`Invalid option '${submittedValue}' for booking field '${
|
||||
eventTypeBookingField.name
|
||||
`Invalid option '${submittedValue}' for booking field '${eventTypeBookingField.name
|
||||
}'. Allowed options are: ${allowedOptionValues.join(", ")}.`
|
||||
);
|
||||
}
|
||||
@@ -464,6 +460,7 @@ export class BookingsService_2024_08_13 {
|
||||
noEmail: bookingRequest.noEmail,
|
||||
areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled,
|
||||
},
|
||||
creationSource: "API_V2",
|
||||
});
|
||||
const ids = bookings.map((booking) => booking.id || 0);
|
||||
return this.outputService.getOutputRecurringBookings(ids);
|
||||
@@ -488,6 +485,7 @@ export class BookingsService_2024_08_13 {
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
areCalendarEventsEnabled: bookingRequest.areCalendarEventsEnabled,
|
||||
},
|
||||
creationSource: "API_V2",
|
||||
});
|
||||
return this.outputService.getOutputCreateRecurringSeatedBookings(
|
||||
bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" })),
|
||||
@@ -921,40 +919,40 @@ export class BookingsService_2024_08_13 {
|
||||
return await this.getBooking(recurringBookingUid, authUser);
|
||||
}
|
||||
|
||||
async markAbsent(
|
||||
bookingUid: string,
|
||||
bookingOwnerId: number,
|
||||
body: MarkAbsentBookingInput_2024_08_13,
|
||||
userUuid?: string
|
||||
) {
|
||||
const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body);
|
||||
const bookingBefore = await this.bookingsRepository.getByUid(bookingUid);
|
||||
async markAbsent(
|
||||
bookingUid: string,
|
||||
bookingOwnerId: number,
|
||||
body: MarkAbsentBookingInput_2024_08_13,
|
||||
userUuid?: string
|
||||
) {
|
||||
const bodyTransformed = this.inputService.transformInputMarkAbsentBooking(body);
|
||||
const bookingBefore = await this.bookingsRepository.getByUid(bookingUid);
|
||||
|
||||
if (!bookingBefore) {
|
||||
throw new NotFoundException(`Booking with uid=${bookingUid} not found.`);
|
||||
}
|
||||
if (!bookingBefore) {
|
||||
throw new NotFoundException(`Booking with uid=${bookingUid} not found.`);
|
||||
}
|
||||
|
||||
const nowUtc = DateTime.utc();
|
||||
const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" });
|
||||
const nowUtc = DateTime.utc();
|
||||
const bookingStartTimeUtc = DateTime.fromJSDate(bookingBefore.startTime, { zone: "utc" });
|
||||
|
||||
if (nowUtc < bookingStartTimeUtc) {
|
||||
throw new BadRequestException(
|
||||
`Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}`
|
||||
);
|
||||
}
|
||||
if (nowUtc < bookingStartTimeUtc) {
|
||||
throw new BadRequestException(
|
||||
`Bookings can only be marked as absent after their scheduled start time. Current time in UTC+0: ${nowUtc.toISO()}, Booking start time in UTC+0: ${bookingStartTimeUtc.toISO()}`
|
||||
);
|
||||
}
|
||||
|
||||
const platformClientParams = bookingBefore?.eventTypeId
|
||||
? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId)
|
||||
: undefined;
|
||||
const platformClientParams = bookingBefore?.eventTypeId
|
||||
? await this.platformBookingsService.getOAuthClientParams(bookingBefore.eventTypeId)
|
||||
: undefined;
|
||||
|
||||
await handleMarkNoShow({
|
||||
bookingUid,
|
||||
attendees: bodyTransformed.attendees,
|
||||
noShowHost: bodyTransformed.noShowHost,
|
||||
userId: bookingOwnerId,
|
||||
userUuid,
|
||||
platformClientParams,
|
||||
});
|
||||
await handleMarkNoShow({
|
||||
bookingUid,
|
||||
attendees: bodyTransformed.attendees,
|
||||
noShowHost: bodyTransformed.noShowHost,
|
||||
userId: bookingOwnerId,
|
||||
userUuid,
|
||||
platformClientParams,
|
||||
});
|
||||
|
||||
const booking = await this.bookingsRepository.getByUidWithAttendeesAndUserAndEvent(bookingUid);
|
||||
|
||||
@@ -1146,6 +1144,8 @@ export class BookingsService_2024_08_13 {
|
||||
recurringEventId: booking.recurringEventId ?? undefined,
|
||||
emailsEnabled,
|
||||
platformClientParams,
|
||||
actionSource: "API_V2",
|
||||
actor: makeUserActor(requestUser.uuid),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1179,6 +1179,8 @@ export class BookingsService_2024_08_13 {
|
||||
reason,
|
||||
emailsEnabled,
|
||||
platformClientParams,
|
||||
actionSource: "API_V2",
|
||||
actor: makeUserActor(requestUser.uuid),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -93,6 +93,10 @@ type DatabaseMetadata = z.infer<typeof bookingMetadataSchema>;
|
||||
export class OutputBookingsService_2024_08_13 {
|
||||
constructor(private readonly bookingsRepository: BookingsRepository_2024_08_13) {}
|
||||
|
||||
private getDisplayEmail(email: string): string {
|
||||
return email.replace(/\+[a-zA-Z0-9]{25}/, "");
|
||||
}
|
||||
|
||||
async getOutputBooking(databaseBooking: DatabaseBooking) {
|
||||
const dateStart = DateTime.fromISO(databaseBooking.startTime.toISOString());
|
||||
const dateEnd = DateTime.fromISO(databaseBooking.endTime.toISOString());
|
||||
@@ -127,6 +131,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
attendees: databaseBooking.attendees.map((attendee) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
displayEmail: this.getDisplayEmail(attendee.email),
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
@@ -149,6 +154,25 @@ export class OutputBookingsService_2024_08_13 {
|
||||
// note(Lauris): I don't know why plainToClass erases bookings responses and metadata so attaching manually
|
||||
bookingTransformed.bookingFieldsResponses = bookingResponses;
|
||||
bookingTransformed.metadata = this.getUserDefinedMetadata(metadata);
|
||||
|
||||
if (
|
||||
bookingTransformed.bookingFieldsResponses?.email &&
|
||||
typeof bookingTransformed.bookingFieldsResponses.email === "string"
|
||||
) {
|
||||
bookingTransformed.bookingFieldsResponses.displayEmail = this.getDisplayEmail(
|
||||
bookingTransformed.bookingFieldsResponses.email
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
bookingTransformed.bookingFieldsResponses?.guests &&
|
||||
Array.isArray(bookingTransformed.bookingFieldsResponses.guests)
|
||||
) {
|
||||
bookingTransformed.bookingFieldsResponses.displayGuests = bookingTransformed.bookingFieldsResponses.guests.map(
|
||||
(guest: string) => this.getDisplayEmail(guest)
|
||||
);
|
||||
}
|
||||
|
||||
return bookingTransformed;
|
||||
}
|
||||
|
||||
@@ -192,12 +216,14 @@ export class OutputBookingsService_2024_08_13 {
|
||||
id: "unknown",
|
||||
name: "unknown",
|
||||
email: "unknown",
|
||||
displayEmail: "unknown",
|
||||
username: "unknown",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...user,
|
||||
displayEmail: this.getDisplayEmail(user.email),
|
||||
username: user.username || "unknown",
|
||||
};
|
||||
}
|
||||
@@ -249,6 +275,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
attendees: databaseBooking.attendees.map((attendee) => ({
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
displayEmail: this.getDisplayEmail(attendee.email),
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
@@ -272,6 +299,25 @@ export class OutputBookingsService_2024_08_13 {
|
||||
// note(Lauris): I don't know why plainToClass erases bookings responses and metadata so attaching manually
|
||||
bookingTransformed.bookingFieldsResponses = bookingResponses;
|
||||
bookingTransformed.metadata = this.getUserDefinedMetadata(metadata);
|
||||
|
||||
if (
|
||||
bookingTransformed.bookingFieldsResponses?.email &&
|
||||
typeof bookingTransformed.bookingFieldsResponses.email === "string"
|
||||
) {
|
||||
bookingTransformed.bookingFieldsResponses.displayEmail = this.getDisplayEmail(
|
||||
bookingTransformed.bookingFieldsResponses.email
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
bookingTransformed.bookingFieldsResponses?.guests &&
|
||||
Array.isArray(bookingTransformed.bookingFieldsResponses.guests)
|
||||
) {
|
||||
bookingTransformed.bookingFieldsResponses.displayGuests = bookingTransformed.bookingFieldsResponses.guests.map(
|
||||
(guest: string) => this.getDisplayEmail(guest)
|
||||
);
|
||||
}
|
||||
|
||||
return bookingTransformed;
|
||||
}
|
||||
|
||||
@@ -337,6 +383,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
displayEmail: this.getDisplayEmail(attendee.email),
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
@@ -465,6 +512,7 @@ export class OutputBookingsService_2024_08_13 {
|
||||
const attendeeData = {
|
||||
name: attendee.name,
|
||||
email: attendee.email,
|
||||
displayEmail: this.getDisplayEmail(attendee.email),
|
||||
timeZone: attendee.timeZone,
|
||||
language: attendee.locale,
|
||||
absent: !!attendee.noShow,
|
||||
@@ -497,6 +545,9 @@ export class OutputBookingsService_2024_08_13 {
|
||||
id: databaseBooking?.user?.id || 0,
|
||||
name: databaseBooking?.user?.name || "unknown",
|
||||
email: databaseBooking?.user?.email || "unknown",
|
||||
displayEmail: databaseBooking?.user?.email
|
||||
? this.getDisplayEmail(databaseBooking.user.email)
|
||||
: "unknown",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import {
|
||||
GOOGLE_CALENDAR,
|
||||
GOOGLE_CALENDAR_ID,
|
||||
GOOGLE_CALENDAR_TYPE,
|
||||
OFFICE_365_CALENDAR,
|
||||
OFFICE_365_CALENDAR_ID,
|
||||
OFFICE_365_CALENDAR_TYPE,
|
||||
SUCCESS_STATUS,
|
||||
} from "@calcom/platform-constants";
|
||||
import { ICS_CALENDAR, ICS_CALENDAR_TYPE } from "@calcom/platform-constants/apps";
|
||||
import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
|
||||
import type { Credential, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-service-mock";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateIcsFeedOutput, CreateIcsFeedOutputResponseDto } from "@/ee/calendars/input/create-ics.output";
|
||||
import { ConnectedCalendarsData } from "@/ee/calendars/outputs/connected-calendars.output";
|
||||
import { DeletedCalendarCredentialsOutputResponseDto } from "@/ee/calendars/outputs/delete-calendar-credentials.output";
|
||||
@@ -9,30 +33,6 @@ import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||
import { IcsCalendarServiceMock } from "test/mocks/ics-calendar-service-mock";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
GOOGLE_CALENDAR,
|
||||
OFFICE_365_CALENDAR,
|
||||
GOOGLE_CALENDAR_TYPE,
|
||||
GOOGLE_CALENDAR_ID,
|
||||
} from "@calcom/platform-constants";
|
||||
import { OFFICE_365_CALENDAR_ID, OFFICE_365_CALENDAR_TYPE } from "@calcom/platform-constants";
|
||||
import { ICS_CALENDAR, ICS_CALENDAR_TYPE } from "@calcom/platform-constants/apps";
|
||||
import { IcsFeedCalendarService } from "@calcom/platform-libraries/app-store";
|
||||
import type { PlatformOAuthClient, Team, User, Credential } from "@calcom/prisma/client";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:5555";
|
||||
|
||||
|
||||
+16
-20
@@ -1,30 +1,28 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { CreatePrivateLinkInput } from "@calcom/platform-types";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { CreatePrivateLinkInput } from "@calcom/platform-types";
|
||||
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
describe("Event Types Private Links Endpoints", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
let oAuthClient: any;
|
||||
let _oAuthClient: any;
|
||||
let organization: any;
|
||||
let userRepositoryFixture: UserRepositoryFixture;
|
||||
let teamRepositoryFixture: TeamRepositoryFixture;
|
||||
@@ -61,7 +59,7 @@ describe("Event Types Private Links Endpoints", () => {
|
||||
name: `private-links-organization-${randomString()}`,
|
||||
slug: `private-links-org-slug-${randomString()}`,
|
||||
});
|
||||
oAuthClient = await createOAuthClient(organization.id);
|
||||
_oAuthClient = await createOAuthClient(organization.id);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
name: `private-links-user-${randomString()}`,
|
||||
@@ -174,5 +172,3 @@ describe("Event Types Private Links Endpoints", () => {
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
+32
-33
@@ -1,43 +1,42 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { Editable } from "@/ee/event-types/event-types_2024_04_15//inputs/enums/editable";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
|
||||
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import {
|
||||
CAL_API_VERSION_HEADER,
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_04_15,
|
||||
VERSION_2024_06_11,
|
||||
} from "@calcom/platform-constants";
|
||||
import {
|
||||
EventTypesByViewer,
|
||||
EventTypesPublic,
|
||||
eventTypeBookingFields,
|
||||
eventTypeLocations,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { EventType, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
SUCCESS_STATUS,
|
||||
VERSION_2024_06_11,
|
||||
VERSION_2024_04_15,
|
||||
CAL_API_VERSION_HEADER,
|
||||
} from "@calcom/platform-constants";
|
||||
import {
|
||||
EventTypesByViewer,
|
||||
eventTypeBookingFields,
|
||||
eventTypeLocations,
|
||||
EventTypesPublic,
|
||||
} from "@calcom/platform-libraries/event-types";
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { EventType, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { Editable } from "@/ee/event-types/event-types_2024_04_15//inputs/enums/editable";
|
||||
import { EventTypesModule_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/event-types.module";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { BaseField } from "@/ee/event-types/event-types_2024_04_15/inputs/enums/field-type";
|
||||
import { UpdateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/update-event-type.input";
|
||||
import { GetEventTypeOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type.output";
|
||||
import { GetEventTypePublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-type-public.output";
|
||||
import { GetEventTypesPublicOutput } from "@/ee/event-types/event-types_2024_04_15/outputs/get-event-types-public.output";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Event types Endpoints", () => {
|
||||
describe("Not authenticated", () => {
|
||||
@@ -429,12 +428,12 @@ describe("Event types Endpoints", () => {
|
||||
await teamRepositoryFixture.delete(organization.id);
|
||||
try {
|
||||
await eventTypesRepositoryFixture.delete(eventType.id);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// Event type might have been deleted by the test
|
||||
}
|
||||
try {
|
||||
await userRepositoryFixture.delete(user.id);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// User might have been deleted by the test
|
||||
}
|
||||
await app.close();
|
||||
|
||||
+47
-38
@@ -1,16 +1,32 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import {
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
FrequencyInput,
|
||||
NoticeThresholdUnitEnum,
|
||||
} from "@calcom/platform-enums";
|
||||
import { SchedulingType } from "@calcom/platform-libraries";
|
||||
import {
|
||||
type ApiSuccessResponse,
|
||||
BaseConfirmationPolicy_2024_06_14,
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type EventTypeOutput_2024_06_14,
|
||||
type GuestsDefaultFieldOutput_2024_06_14,
|
||||
type NameDefaultFieldInput_2024_06_14,
|
||||
type NotesDefaultFieldInput_2024_06_14,
|
||||
type SplitNameDefaultFieldOutput_2024_06_14,
|
||||
supportedIntegrations,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
type UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { FAILED_RECURRING_EVENT_TYPE_WITH_BOOKER_LIMITS_ERROR_MESSAGE } from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/validators/CantHaveRecurrenceAndBookerActiveBookingsLimit";
|
||||
import { REQUIRES_AT_LEAST_ONE_PROPERTY_ERROR } from "@calcom/platform-types/utils/RequiresOneOfPropertiesWhenNotDisabled";
|
||||
import type { EventType, PlatformOAuthClient, Schedule, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { ApiKeysRepositoryFixture } from "test/fixtures/repository/api-keys.repository.fixture";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
@@ -21,32 +37,15 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_14 } from "@calcom/platform-constants";
|
||||
import {
|
||||
BookingWindowPeriodInputTypeEnum_2024_06_14,
|
||||
BookerLayoutsInputEnum_2024_06_14,
|
||||
ConfirmationPolicyEnum,
|
||||
NoticeThresholdUnitEnum,
|
||||
FrequencyInput,
|
||||
} from "@calcom/platform-enums";
|
||||
import { SchedulingType } from "@calcom/platform-libraries";
|
||||
import {
|
||||
BaseConfirmationPolicy_2024_06_14,
|
||||
TeamEventTypeOutput_2024_06_14,
|
||||
supportedIntegrations,
|
||||
type ApiSuccessResponse,
|
||||
type CreateEventTypeInput_2024_06_14,
|
||||
type EventTypeOutput_2024_06_14,
|
||||
type GuestsDefaultFieldOutput_2024_06_14,
|
||||
type NameDefaultFieldInput_2024_06_14,
|
||||
type NotesDefaultFieldInput_2024_06_14,
|
||||
type SplitNameDefaultFieldOutput_2024_06_14,
|
||||
type UpdateEventTypeInput_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import { FAILED_RECURRING_EVENT_TYPE_WITH_BOOKER_LIMITS_ERROR_MESSAGE } from "@calcom/platform-types/event-types/event-types_2024_06_14/inputs/validators/CantHaveRecurrenceAndBookerActiveBookingsLimit";
|
||||
import { REQUIRES_AT_LEAST_ONE_PROPERTY_ERROR } from "@calcom/platform-types/utils/RequiresOneOfPropertiesWhenNotDisabled";
|
||||
import type { PlatformOAuthClient, Team, User, Schedule, EventType } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateEventTypeInput_2024_04_15 } from "@/ee/event-types/event-types_2024_04_15/inputs/create-event-type.input";
|
||||
import { EventTypesModule_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/event-types.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
const orderBySlug = (a: { slug: string }, b: { slug: string }) => {
|
||||
if (a.slug < b.slug) return -1;
|
||||
@@ -636,6 +635,7 @@ describe("Event types Endpoints", () => {
|
||||
|
||||
expect(createdEventType.bookingFields).toEqual(expectedBookingFields);
|
||||
expect(createdEventType.bookingRequiresAuthentication).toEqual(true);
|
||||
expect(createdEventType.bookingUrl).toContain(`/${user.username}/${body.slug}`);
|
||||
expect(createdEventType.disableCancelling).toEqual({ disabled: true });
|
||||
expect(createdEventType.disableRescheduling).toEqual({ disabled: true });
|
||||
expect(createdEventType.calVideoSettings?.sendTranscriptionEmails).toEqual(true);
|
||||
@@ -732,6 +732,7 @@ describe("Event types Endpoints", () => {
|
||||
);
|
||||
expect(fetchedEventType?.color).toEqual(eventType.color);
|
||||
expect(fetchedEventType?.hidden).toEqual(false);
|
||||
expect(fetchedEventType?.bookingUrl).toContain(`/${user.username}/${eventType.slug}`);
|
||||
expect(fetchedEventType?.disableCancelling).toEqual(eventType.disableCancelling);
|
||||
expect(fetchedEventType?.disableRescheduling).toEqual(eventType.disableRescheduling);
|
||||
expect(fetchedEventType?.calVideoSettings?.sendTranscriptionEmails).toEqual(
|
||||
@@ -1552,6 +1553,7 @@ describe("Event types Endpoints", () => {
|
||||
eventType.lockTimeZoneToggleOnBookingPage
|
||||
);
|
||||
expect(fetchedEventType.color).toEqual(eventType.color);
|
||||
expect(fetchedEventType.bookingUrl).toContain(`/${user.username}/${eventType.slug}`);
|
||||
});
|
||||
|
||||
it("system admin can access another user's event type by id", async () => {
|
||||
@@ -2678,12 +2680,19 @@ describe("Event types Endpoints", () => {
|
||||
expect(createdEventType.title).toEqual(body.title);
|
||||
secondCreatedEventType = responseBody.data;
|
||||
|
||||
const { id, title, slug, ...restFirst } = firstCreatedEventType;
|
||||
const { id: id2, title: title2, slug: slug2, ...restSecond } = secondCreatedEventType;
|
||||
const { id, title, slug, bookingUrl, ...restFirst } = firstCreatedEventType;
|
||||
const {
|
||||
id: id2,
|
||||
title: title2,
|
||||
slug: slug2,
|
||||
bookingUrl: bookingUrl2,
|
||||
...restSecond
|
||||
} = secondCreatedEventType;
|
||||
expect(restFirst).toEqual(restSecond);
|
||||
expect(id2).not.toEqual(id);
|
||||
expect(title2).not.toEqual(title);
|
||||
expect(slug2).not.toEqual(slug);
|
||||
expect(bookingUrl2).not.toEqual(bookingUrl);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import type { SortOrderType } from "@calcom/platform-types";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { InputEventTransformed_2024_06_14 } from "@/ee/event-types/event-types_2024_06_14/transformed";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import type { SortOrderType } from "@calcom/platform-types";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class EventTypesRepository_2024_06_14 {
|
||||
constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {}
|
||||
constructor(
|
||||
private readonly dbRead: PrismaReadService,
|
||||
private readonly dbWrite: PrismaWriteService
|
||||
) {}
|
||||
|
||||
async createUserEventType(
|
||||
userId: number,
|
||||
@@ -58,13 +60,59 @@ export class EventTypesRepository_2024_06_14 {
|
||||
});
|
||||
}
|
||||
|
||||
private readonly usersInclude = {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
username: true,
|
||||
isPlatformManaged: true,
|
||||
avatarUrl: true,
|
||||
brandColor: true,
|
||||
darkBrandColor: true,
|
||||
weekStart: true,
|
||||
metadata: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: { slug: true },
|
||||
},
|
||||
movedToProfile: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
isPlatform: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
profiles: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
organizationId: true,
|
||||
organization: {
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
isPlatform: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async getUserEventType(userId: number, eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findFirst({
|
||||
where: {
|
||||
id: eventTypeId,
|
||||
userId,
|
||||
},
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
include: { users: this.usersInclude, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -74,7 +122,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
userId,
|
||||
},
|
||||
...(sortCreatedAt && { orderBy: { id: sortCreatedAt } }),
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
include: { users: this.usersInclude, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -85,14 +133,19 @@ export class EventTypesRepository_2024_06_14 {
|
||||
hidden: false,
|
||||
},
|
||||
...(sortCreatedAt && { orderBy: { id: sortCreatedAt } }),
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
include: { users: this.usersInclude, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
async getEventTypeById(eventTypeId: number) {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: { users: true, schedule: true, destinationCalendar: true, calVideoSettings: true },
|
||||
include: {
|
||||
users: this.usersInclude,
|
||||
schedule: true,
|
||||
destinationCalendar: true,
|
||||
calVideoSettings: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -100,7 +153,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
return this.dbRead.prisma.eventType.findUnique({
|
||||
where: { id: eventTypeId },
|
||||
include: {
|
||||
users: true,
|
||||
users: this.usersInclude,
|
||||
schedule: true,
|
||||
destinationCalendar: true,
|
||||
calVideoSettings: true,
|
||||
@@ -140,7 +193,7 @@ export class EventTypesRepository_2024_06_14 {
|
||||
slug: slug,
|
||||
},
|
||||
},
|
||||
include: { users: true, schedule: true, destinationCalendar: true },
|
||||
include: { users: this.usersInclude, schedule: true, destinationCalendar: true },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
import { OutputEventTypesService_2024_06_14 } from "./output-event-types.service";
|
||||
import { UsersService } from "@/modules/users/services/users.service";
|
||||
import { UsersRepository } from "@/modules/users/users.repository";
|
||||
|
||||
jest.mock("@calcom/platform-libraries/organizations", () => ({
|
||||
getBookerBaseUrlSync: jest.fn((slug: string | null) => {
|
||||
if (!slug) return "https://cal.com";
|
||||
return `https://${slug}.cal.com`;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe("OutputEventTypesService_2024_06_14", () => {
|
||||
let service: OutputEventTypesService_2024_06_14;
|
||||
let configService: jest.Mocked<ConfigService>;
|
||||
let usersService: UsersService;
|
||||
|
||||
beforeEach(() => {
|
||||
configService = {
|
||||
get: jest.fn((key: string, defaultValue?: string) => {
|
||||
if (key === "app.baseUrl") return "https://cal.com";
|
||||
return defaultValue;
|
||||
}),
|
||||
} as any;
|
||||
|
||||
const usersRepository = {} as UsersRepository;
|
||||
usersService = new UsersService(usersRepository);
|
||||
|
||||
service = new OutputEventTypesService_2024_06_14(configService, usersService);
|
||||
});
|
||||
|
||||
describe("buildBookingUrl", () => {
|
||||
it("should return correct URL for user without organization", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "John Doe",
|
||||
username: "john-doe",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
movedToProfile: null,
|
||||
profiles: [],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://cal.com/john-doe/30min");
|
||||
});
|
||||
|
||||
it("should use profile username for org users", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "Owner",
|
||||
username: "owner1-acme",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 1,
|
||||
organization: { slug: "acme" },
|
||||
movedToProfile: null,
|
||||
profiles: [
|
||||
{
|
||||
id: 100,
|
||||
username: "owner1",
|
||||
organizationId: 1,
|
||||
organization: { id: 1, slug: "acme", isPlatform: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://acme.cal.com/owner1/30min");
|
||||
});
|
||||
|
||||
it("should use cal.com for non-managed users in platform orgs", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "Dhairyashil Shinde",
|
||||
username: "dhairyashil10101010-gmail-com",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 1,
|
||||
organization: { slug: "gmail-platform-9041df0e" },
|
||||
movedToProfile: null,
|
||||
profiles: [
|
||||
{
|
||||
id: 100,
|
||||
username: "dhairyashil",
|
||||
organizationId: 1,
|
||||
organization: { id: 1, slug: "gmail-platform-9041df0e", isPlatform: true },
|
||||
},
|
||||
],
|
||||
};
|
||||
const slug = "secret";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://cal.com/dhairyashil/secret");
|
||||
});
|
||||
|
||||
it("should return empty string for managed users", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "Managed User",
|
||||
username: "managed-user-abc123",
|
||||
isPlatformManaged: true,
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 1,
|
||||
organization: { slug: "platform-org" },
|
||||
movedToProfile: null,
|
||||
profiles: [],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("should fall back to user username when profile has no username", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "Keith",
|
||||
username: "keith",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 2,
|
||||
organization: { slug: "i" },
|
||||
movedToProfile: null,
|
||||
profiles: [
|
||||
{
|
||||
id: 200,
|
||||
username: null,
|
||||
organizationId: 2,
|
||||
organization: { id: 2, slug: "i", isPlatform: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://i.cal.com/keith/30min");
|
||||
});
|
||||
|
||||
it("should return empty string when user is undefined", () => {
|
||||
const user = undefined;
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("should handle organization with null slug", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "User",
|
||||
username: "user",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 3,
|
||||
organization: { slug: null },
|
||||
movedToProfile: null,
|
||||
profiles: [
|
||||
{
|
||||
id: 300,
|
||||
username: "user",
|
||||
organizationId: 3,
|
||||
organization: { id: 3, slug: null, isPlatform: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
const slug = "consultation";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://cal.com/user/consultation");
|
||||
});
|
||||
|
||||
it("should handle base URL with trailing slash", () => {
|
||||
const { getBookerBaseUrlSync } = require("@calcom/platform-libraries/organizations");
|
||||
getBookerBaseUrlSync.mockReturnValueOnce("https://acme.cal.com/");
|
||||
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "John",
|
||||
username: "john-acme",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: 1,
|
||||
organization: { slug: "acme" },
|
||||
movedToProfile: null,
|
||||
profiles: [
|
||||
{
|
||||
id: 400,
|
||||
username: "john",
|
||||
organizationId: 1,
|
||||
organization: { id: 1, slug: "acme", isPlatform: false },
|
||||
},
|
||||
],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("https://acme.cal.com/john/30min");
|
||||
});
|
||||
|
||||
it("should return empty string when username is empty", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "User",
|
||||
username: "",
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
movedToProfile: null,
|
||||
profiles: [],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
it("should return empty string when username is null", () => {
|
||||
const user = {
|
||||
id: 1,
|
||||
name: "User",
|
||||
username: null,
|
||||
avatarUrl: null,
|
||||
brandColor: null,
|
||||
darkBrandColor: null,
|
||||
weekStart: "Monday",
|
||||
metadata: {},
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
movedToProfile: null,
|
||||
profiles: [],
|
||||
};
|
||||
const slug = "30min";
|
||||
|
||||
const result = service.buildBookingUrl(user, slug);
|
||||
|
||||
expect(result).toBe("");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+86
-27
@@ -1,41 +1,66 @@
|
||||
import {
|
||||
transformLocationsInternalToApi,
|
||||
transformBookingFieldsInternalToApi,
|
||||
InternalLocationSchema,
|
||||
SystemField,
|
||||
CustomField,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
transformBookerLayoutsInternalToApi,
|
||||
transformRequiresConfirmationInternalToApi,
|
||||
transformEventTypeColorsInternalToApi,
|
||||
transformSeatsInternalToApi,
|
||||
InternalLocation,
|
||||
BookingFieldSchema,
|
||||
} from "@/ee/event-types/event-types_2024_06_14/transformers";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
userMetadata,
|
||||
getBookingFieldsWithSystemFields,
|
||||
parseBookingLimit,
|
||||
parseRecurringEvent,
|
||||
getBookingFieldsWithSystemFields,
|
||||
userMetadata,
|
||||
} from "@calcom/platform-libraries";
|
||||
import { EventTypeMetaDataSchema, parseEventTypeColor } from "@calcom/platform-libraries/event-types";
|
||||
import { getBookerBaseUrlSync } from "@calcom/platform-libraries/organizations";
|
||||
import type {
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
BookerLayoutsTransformedSchema,
|
||||
NoticeThresholdTransformedSchema,
|
||||
EventTypeOutput_2024_06_14,
|
||||
OutputUnknownLocation_2024_06_14,
|
||||
OutputUnknownBookingField_2024_06_14,
|
||||
NoticeThresholdTransformedSchema,
|
||||
OutputBookingField_2024_06_14,
|
||||
OutputUnknownBookingField_2024_06_14,
|
||||
OutputUnknownLocation_2024_06_14,
|
||||
TransformFutureBookingsLimitSchema_2024_06_14,
|
||||
} from "@calcom/platform-types";
|
||||
import type { EventType, User, Schedule, DestinationCalendar, CalVideoSettings } from "@calcom/prisma/client";
|
||||
import type {
|
||||
CalVideoSettings,
|
||||
DestinationCalendar,
|
||||
EventType,
|
||||
Prisma,
|
||||
Schedule,
|
||||
Team,
|
||||
} from "@calcom/prisma/client";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import {
|
||||
BookingFieldSchema,
|
||||
CustomField,
|
||||
InternalLocation,
|
||||
InternalLocationSchema,
|
||||
SystemField,
|
||||
transformBookerLayoutsInternalToApi,
|
||||
transformBookingFieldsInternalToApi,
|
||||
transformEventTypeColorsInternalToApi,
|
||||
transformFutureBookingLimitsInternalToApi,
|
||||
transformIntervalLimitsInternalToApi,
|
||||
transformLocationsInternalToApi,
|
||||
transformRecurrenceInternalToApi,
|
||||
transformRequiresConfirmationInternalToApi,
|
||||
transformSeatsInternalToApi,
|
||||
} from "@/ee/event-types/event-types_2024_06_14/transformers";
|
||||
import { ProfileMinimal, UsersService } from "@/modules/users/services/users.service";
|
||||
|
||||
type EventTypeUser = {
|
||||
id: number;
|
||||
name: string | null;
|
||||
username: string | null;
|
||||
isPlatformManaged?: boolean;
|
||||
avatarUrl: string | null;
|
||||
brandColor: string | null;
|
||||
darkBrandColor: string | null;
|
||||
weekStart: string;
|
||||
metadata: Prisma.JsonValue;
|
||||
organizationId: number | null;
|
||||
organization?: { slug: string | null; isPlatform?: boolean } | null;
|
||||
movedToProfile?: ProfileMinimal | null;
|
||||
profiles?: ProfileMinimal[];
|
||||
};
|
||||
|
||||
type EventTypeRelations = {
|
||||
users: User[];
|
||||
users: EventTypeUser[];
|
||||
schedule: Schedule | null;
|
||||
destinationCalendar?: DestinationCalendar | null;
|
||||
calVideoSettings?: CalVideoSettings | null;
|
||||
@@ -106,6 +131,11 @@ type Input = Pick<
|
||||
|
||||
@Injectable()
|
||||
export class OutputEventTypesService_2024_06_14 {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly usersService: UsersService
|
||||
) {}
|
||||
|
||||
getResponseEventType(
|
||||
ownerId: number,
|
||||
databaseEventType: Input,
|
||||
@@ -184,6 +214,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
} as TransformFutureBookingsLimitSchema_2024_06_14);
|
||||
const destinationCalendar = this.transformDestinationCalendar(databaseEventType.destinationCalendar);
|
||||
const bookerActiveBookingsLimit = this.transformBookerActiveBookingsLimit(databaseEventType);
|
||||
const bookingUrl = this.buildBookingUrl(databaseEventType.users[0], slug);
|
||||
const disableReschedulingOutput = this.transformDisableRescheduling(
|
||||
disableRescheduling,
|
||||
minimumRescheduleNotice
|
||||
@@ -239,6 +270,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
hidden,
|
||||
bookingRequiresAuthentication,
|
||||
bookerActiveBookingsLimit,
|
||||
bookingUrl,
|
||||
disableCancelling: disableCancellingOutput,
|
||||
disableRescheduling: disableReschedulingOutput,
|
||||
interfaceLanguage,
|
||||
@@ -342,7 +374,7 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
return EventTypeMetaDataSchema.parse(metadata);
|
||||
}
|
||||
|
||||
transformUsers(users: User[]) {
|
||||
transformUsers(users: EventTypeUser[]) {
|
||||
return users.map((user) => {
|
||||
const metadata = user.metadata ? userMetadata.parse(user.metadata) : {};
|
||||
return {
|
||||
@@ -405,6 +437,33 @@ export class OutputEventTypesService_2024_06_14 {
|
||||
});
|
||||
}
|
||||
|
||||
buildBookingUrl(user: EventTypeUser | undefined, slug: string): string {
|
||||
if (!user) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Managed users don't have public booking pages
|
||||
if (user.isPlatformManaged) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const profile = this.usersService.getUserMainProfile(user);
|
||||
const username = profile?.username ?? user.username;
|
||||
|
||||
if (!username) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const org = profile?.organization;
|
||||
// Don't use org subdomain for platform organizations - they don't have public-facing subdomains
|
||||
const orgSlug = org && !org.isPlatform ? org.slug : null;
|
||||
const webAppUrl = this.configService.get<string>("app.baseUrl", "https://app.cal.com");
|
||||
const baseUrl = orgSlug ? getBookerBaseUrlSync(orgSlug) : webAppUrl;
|
||||
const normalizedBaseUrl = baseUrl.replace(/\/$/, "");
|
||||
|
||||
return `${normalizedBaseUrl}/${username}/${slug}`;
|
||||
}
|
||||
|
||||
getResponseEventTypesWithoutHiddenFields(
|
||||
eventTypes: EventTypeOutput_2024_06_14[]
|
||||
): EventTypeOutput_2024_06_14[] {
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import type { Credential, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { CalendarsServiceMock } from "test/mocks/calendars-service-mock";
|
||||
|
||||
import type { PlatformOAuthClient, Team, User, Credential } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:5555";
|
||||
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { ApiSuccessResponse, UserResponse } from "@calcom/platform-types";
|
||||
import type { Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { UserResponse, ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { User, Team } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { SchedulesModule_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/schedules.module";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Me Endpoints", () => {
|
||||
describe("User Authentication", () => {
|
||||
|
||||
+13
-14
@@ -1,5 +1,16 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_04_15 } from "@calcom/platform-constants";
|
||||
import type { UpdateScheduleInput_2024_04_15 } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateScheduleInput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/inputs/create-schedule.input";
|
||||
import { CreateScheduleOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/create-schedule.output";
|
||||
import { GetSchedulesOutput_2024_04_15 } from "@/ee/schedules/schedules_2024_04_15/outputs/get-schedules.output";
|
||||
@@ -9,18 +20,6 @@ import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_04_15 } from "@calcom/platform-constants";
|
||||
import type { UpdateScheduleInput_2024_04_15 } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
|
||||
describe("Schedules Endpoints", () => {
|
||||
describe("User Authentication", () => {
|
||||
@@ -214,7 +213,7 @@ describe("Schedules Endpoints", () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
try {
|
||||
await scheduleRepositoryFixture.deleteById(createdSchedule.id);
|
||||
} catch (e) {}
|
||||
} catch (_e) {}
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
+16
-17
@@ -1,18 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import { CAL_API_VERSION_HEADER, SUCCESS_STATUS, VERSION_2024_06_11 } from "@calcom/platform-constants";
|
||||
import type {
|
||||
CreateScheduleInput_2024_06_11,
|
||||
@@ -20,10 +5,24 @@ import type {
|
||||
GetScheduleOutput_2024_06_11,
|
||||
GetSchedulesOutput_2024_06_11,
|
||||
ScheduleOutput_2024_06_11,
|
||||
UpdateScheduleOutput_2024_06_11,
|
||||
UpdateScheduleInput_2024_06_11,
|
||||
UpdateScheduleOutput_2024_06_11,
|
||||
} from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { SchedulesModule_2024_06_11 } from "@/ee/schedules/schedules_2024_06_11/schedules.module";
|
||||
import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.guard";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Schedules Endpoints", () => {
|
||||
describe("User Authentication", () => {
|
||||
@@ -259,7 +258,7 @@ describe("Schedules Endpoints", () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
try {
|
||||
await scheduleRepositoryFixture.deleteById(createdSchedule.id);
|
||||
} catch (e) {}
|
||||
} catch (_e) {}
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
+23
-11
@@ -4,18 +4,34 @@ import { Injectable } from "@nestjs/common";
|
||||
import type { WeekDay } from "@calcom/platform-types";
|
||||
import type { Availability, Schedule } from "@calcom/prisma/client";
|
||||
|
||||
type DatabaseSchedule = Schedule & { availability: Availability[] };
|
||||
|
||||
@Injectable()
|
||||
export class OutputSchedulesService_2024_06_11 {
|
||||
constructor(private readonly usersRepository: UsersRepository) {}
|
||||
|
||||
async getResponseSchedule(databaseSchedule: Schedule & { availability: Availability[] }) {
|
||||
if (!databaseSchedule.timeZone) {
|
||||
databaseSchedule.timeZone = "Europe/London";
|
||||
}
|
||||
async getResponseSchedules(databaseSchedules: DatabaseSchedule[]) {
|
||||
if (databaseSchedules.length === 0) return [];
|
||||
const userIds = [...new Set(databaseSchedules.map((schedule) => schedule.userId))];
|
||||
const defaultScheduleIds = await this.usersRepository.getUsersScheduleDefaultIds(userIds);
|
||||
|
||||
return databaseSchedules.map((schedule) =>
|
||||
this.transformScheduleToOutput(schedule, defaultScheduleIds.get(schedule.userId) ?? null)
|
||||
);
|
||||
}
|
||||
|
||||
async getResponseSchedule(databaseSchedule: DatabaseSchedule) {
|
||||
const ownerDefaultScheduleId = await this.usersRepository.getUserScheduleDefaultId(
|
||||
databaseSchedule.userId
|
||||
);
|
||||
return this.transformScheduleToOutput(databaseSchedule, ownerDefaultScheduleId);
|
||||
}
|
||||
|
||||
private transformScheduleToOutput(
|
||||
databaseSchedule: DatabaseSchedule,
|
||||
ownerDefaultScheduleId: number | null
|
||||
) {
|
||||
const timeZone = databaseSchedule.timeZone || "Europe/London";
|
||||
|
||||
const createdScheduleAvailabilities = databaseSchedule.availability.filter(
|
||||
(availability) => !!availability.days.length
|
||||
@@ -26,7 +42,7 @@ export class OutputSchedulesService_2024_06_11 {
|
||||
id: databaseSchedule.id,
|
||||
ownerId: databaseSchedule.userId,
|
||||
name: databaseSchedule.name,
|
||||
timeZone: databaseSchedule.timeZone,
|
||||
timeZone,
|
||||
availability: createdScheduleAvailabilities.map((availability) => ({
|
||||
days: availability.days.map(transformNumberToDay),
|
||||
startTime: this.padHoursMinutesWithZeros(
|
||||
@@ -54,13 +70,9 @@ export class OutputSchedulesService_2024_06_11 {
|
||||
};
|
||||
}
|
||||
|
||||
padHoursMinutesWithZeros(hhMM: string) {
|
||||
private padHoursMinutesWithZeros(hhMM: string) {
|
||||
const [hours, minutes] = hhMM.split(":");
|
||||
|
||||
const formattedHours = hours.padStart(2, "0");
|
||||
const formattedMinutes = minutes.padStart(2, "0");
|
||||
|
||||
return `${formattedHours}:${formattedMinutes}`;
|
||||
return `${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ export type Environment = {
|
||||
DATABASE_WRITE_POOL_MAX: number;
|
||||
DATABASE_READ_WORKER_POOL_MAX: number;
|
||||
DATABASE_WRITE_WORKER_POOL_MAX: number;
|
||||
ENABLE_SLOTS_WORKERS: string;
|
||||
SLOTS_WORKER_POOL_SIZE: string;
|
||||
USE_POOL: string;
|
||||
};
|
||||
|
||||
export const getEnv = <K extends keyof Environment>(key: K, fallback?: Environment[K]): Environment[K] => {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { BusyTimesService } from "@/lib/services/busy-times.service";
|
||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
||||
import { FilterHostsService } from "@/lib/services/filter-hosts.service";
|
||||
import { NoSlotsNotificationService } from "@/lib/services/no-slots-notification.service";
|
||||
import { OrgMembershipLookupService } from "@/lib/services/org-membership-lookup.service";
|
||||
import { QualifiedHostsService } from "@/lib/services/qualified-hosts.service";
|
||||
import { UserAvailabilityService } from "@/lib/services/user-availability.service";
|
||||
import { PrismaWorkerModule } from "@/modules/prisma/prisma-worker.module";
|
||||
@@ -42,6 +43,7 @@ import { Module } from "@nestjs/common";
|
||||
FilterHostsService,
|
||||
QualifiedHostsService,
|
||||
NoSlotsNotificationService,
|
||||
OrgMembershipLookupService,
|
||||
],
|
||||
exports: [AvailableSlotsService],
|
||||
})
|
||||
|
||||
@@ -1,10 +1,30 @@
|
||||
import { RegularBookingModule } from "@/lib/modules/regular-booking.module";
|
||||
import { RecurringBookingService } from "@/lib/services/recurring-booking.service";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service";
|
||||
import { Logger } from "@/lib/logger.bridge";
|
||||
import { Scope } from "@nestjs/common";
|
||||
import { BookingAuditProducerService } from "@/lib/services/booking-audit-producer.service";
|
||||
import { HashedLinkService } from "@/lib/services/hashed-link.service";
|
||||
import { TaskerService } from "@/lib/services/tasker.service";
|
||||
@Module({
|
||||
imports: [RegularBookingModule],
|
||||
providers: [RecurringBookingService],
|
||||
providers: [
|
||||
RecurringBookingService,
|
||||
BookingEventHandlerService,
|
||||
/** Required by BookingEventHandlerService - Starts **/
|
||||
HashedLinkService,
|
||||
{
|
||||
provide: Logger,
|
||||
useFactory: () => {
|
||||
return new Logger();
|
||||
},
|
||||
scope: Scope.TRANSIENT,
|
||||
},
|
||||
BookingAuditProducerService,
|
||||
TaskerService,
|
||||
/** Required by BookingEventHandlerService - Ends **/
|
||||
],
|
||||
exports: [RecurringBookingService],
|
||||
})
|
||||
export class RecurringBookingModule {}
|
||||
export class RecurringBookingModule { }
|
||||
|
||||
@@ -10,6 +10,7 @@ import { PrismaUserRepository } from "@/lib/repositories/prisma-user.repository"
|
||||
import { BusyTimesService } from "@/lib/services/busy-times.service";
|
||||
import { CheckBookingLimitsService } from "@/lib/services/check-booking-limits.service";
|
||||
import { NoSlotsNotificationService } from "@/lib/services/no-slots-notification.service";
|
||||
import { OrgMembershipLookupService } from "@/lib/services/org-membership-lookup.service";
|
||||
import { QualifiedHostsService } from "@/lib/services/qualified-hosts.service";
|
||||
import { RedisService } from "@/modules/redis/redis.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
@@ -35,7 +36,8 @@ export class AvailableSlotsService extends BaseAvailableSlotsService {
|
||||
checkBookingLimitsService: CheckBookingLimitsService,
|
||||
userAvailabilityService: UserAvailabilityService,
|
||||
busyTimesService: BusyTimesService,
|
||||
noSlotsNotificationService: NoSlotsNotificationService
|
||||
noSlotsNotificationService: NoSlotsNotificationService,
|
||||
orgMembershipLookupService: OrgMembershipLookupService
|
||||
) {
|
||||
super({
|
||||
oooRepo: oooRepoDependency,
|
||||
@@ -53,6 +55,7 @@ export class AvailableSlotsService extends BaseAvailableSlotsService {
|
||||
qualifiedHostsService,
|
||||
featuresRepo: featuresRepository,
|
||||
noSlotsNotificationService,
|
||||
orgMembershipLookup: orgMembershipLookupService,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import {
|
||||
type OrgMembershipLookup,
|
||||
ProfileRepository,
|
||||
} from "@calcom/platform-libraries";
|
||||
|
||||
/**
|
||||
* NestJS service that implements OrgMembershipLookup interface.
|
||||
* Wraps ProfileRepository's static method for DI compatibility.
|
||||
*/
|
||||
@Injectable()
|
||||
export class OrgMembershipLookupService implements OrgMembershipLookup {
|
||||
async findFirstOrganizationIdForUser({
|
||||
userId,
|
||||
}: {
|
||||
userId: number;
|
||||
}): Promise<number | null> {
|
||||
return ProfileRepository.findFirstOrganizationIdForUser({ userId });
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
import { RegularBookingService } from "@/lib/services/regular-booking.service";
|
||||
import { BookingEventHandlerService } from "@/lib/services/booking-event-handler.service";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
import { RecurringBookingService as BaseRecurringBookingService } from "@calcom/platform-libraries/bookings";
|
||||
|
||||
@Injectable()
|
||||
export class RecurringBookingService extends BaseRecurringBookingService {
|
||||
constructor(regularBookingService: RegularBookingService) {
|
||||
constructor(
|
||||
regularBookingService: RegularBookingService,
|
||||
bookingEventHandler: BookingEventHandlerService
|
||||
) {
|
||||
super({
|
||||
regularBookingService,
|
||||
bookingEventHandler,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+97
-24
@@ -1,46 +1,119 @@
|
||||
import type { AppConfig } from "@/config/type";
|
||||
import "dotenv/config";
|
||||
|
||||
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
||||
|
||||
import { Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import type { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import "dotenv/config";
|
||||
import type { Express, Request, Response } from "express";
|
||||
import { WinstonModule } from "nest-winston";
|
||||
import * as qs from "qs";
|
||||
import qs from "qs";
|
||||
import type { AppConfig } from "@/config/type";
|
||||
|
||||
import { bootstrap } from "./app";
|
||||
import { AppModule } from "./app.module";
|
||||
import { bootstrap } from "./bootstrap";
|
||||
import { loggerConfig } from "./lib/logger";
|
||||
import { generateSwaggerForApp } from "./swagger/generate-swagger";
|
||||
|
||||
run().catch((error: Error) => {
|
||||
console.error("Failed to start Cal Platform API", { error: error.stack });
|
||||
process.exit(1);
|
||||
});
|
||||
const logger: Logger = new Logger("App");
|
||||
|
||||
async function run() {
|
||||
const app = await createNestApp();
|
||||
const logger = new Logger("App");
|
||||
/**
|
||||
* Singleton Class to manage the NestJS App instance.
|
||||
* Ensures we only initialize the app once per container lifecycle.
|
||||
*/
|
||||
class NestServer {
|
||||
private static server: Express; // The underlying Express instance
|
||||
|
||||
try {
|
||||
bootstrap(app);
|
||||
const port = app.get(ConfigService<AppConfig, true>).get("api.port", { infer: true });
|
||||
generateSwaggerForApp(app);
|
||||
await app.listen(port);
|
||||
logger.log(`Application started on port: ${port}`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
logger.error("Application crashed", {
|
||||
error,
|
||||
});
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* Returns the cached server instance.
|
||||
* If it doesn't exist, it creates, bootstraps, and initializes it.
|
||||
*/
|
||||
public static async getInstance(): Promise<Express> {
|
||||
if (!NestServer.server) {
|
||||
const app = await createNestApp();
|
||||
|
||||
// Execute bootstrap (Pipes, Interceptors, CORS, etc.)
|
||||
bootstrap(app);
|
||||
|
||||
// Initialize the app (connects to DB, resolves modules)
|
||||
await app.init();
|
||||
|
||||
// extract the Express instance to pass to Vercel
|
||||
NestServer.server = app.getHttpAdapter().getInstance();
|
||||
}
|
||||
return NestServer.server;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createNestApp() {
|
||||
// -----------------------------------------------------------------------------
|
||||
// LOCAL DEVELOPMENT STARTUP
|
||||
// -----------------------------------------------------------------------------
|
||||
if (!process.env.VERCEL) {
|
||||
run().catch((error: Error) => {
|
||||
logger.error("Failed to start Cal Platform API", { error: error.stack });
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const app = await createNestApp();
|
||||
try {
|
||||
bootstrap(app);
|
||||
const config = app.get(ConfigService<AppConfig, true>);
|
||||
const port = config.get("api.port", { infer: true });
|
||||
|
||||
if (config.get("env.type", { infer: true }) === "development") {
|
||||
const { generateSwaggerForApp } = await import("./swagger/generate-swagger");
|
||||
generateSwaggerForApp(app);
|
||||
}
|
||||
|
||||
await app.listen(port);
|
||||
logger.log(`Application started locally on port: ${port}`);
|
||||
} catch (error) {
|
||||
logger.error("Application crashed during local startup", { error });
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// VERCEL SERVERLESS HANDLER
|
||||
// -----------------------------------------------------------------------------
|
||||
export default async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const server = await NestServer.getInstance();
|
||||
|
||||
// Vercel/AWS specific: Re-parse query strings to support array formats
|
||||
// (e.g., ?ids[]=1&ids[]=2) which Vercel's native parser might simplify.
|
||||
if (req.url) {
|
||||
const [_path, queryString] = req.url.split("?");
|
||||
if (queryString) {
|
||||
req.query = qs.parse(queryString, { arrayLimit: 1000 });
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate request to the cached Express instance
|
||||
return server(req, res);
|
||||
} catch (error) {
|
||||
logger.error("Critical: Failed to initialize NestJS Serverless instance", error);
|
||||
res.statusCode = 500;
|
||||
res.end("Internal Server Error: Initialization Failed");
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// APP FACTORY
|
||||
// -----------------------------------------------------------------------------
|
||||
export async function createNestApp(): Promise<
|
||||
NestExpressApplication<Server<typeof IncomingMessage, typeof ServerResponse>>
|
||||
> {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
|
||||
logger: WinstonModule.createLogger(loggerConfig()),
|
||||
// Preserved as requested:
|
||||
bodyParser: false,
|
||||
});
|
||||
|
||||
// Custom query parser configuration for the underlying Express app
|
||||
app.set("query parser", (str: string) => qs.parse(str, { arrayLimit: 1000 }));
|
||||
|
||||
return app;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Injectable, NestMiddleware } from "@nestjs/common";
|
||||
import * as bodyParser from "body-parser";
|
||||
import { json } from "body-parser";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
@Injectable()
|
||||
export class JsonBodyMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: () => any) {
|
||||
bodyParser.json()(req, res, next);
|
||||
use(req: Request, res: Response, next: () => any): void {
|
||||
json()(req, res, next);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Injectable, NestMiddleware } from "@nestjs/common";
|
||||
import * as bodyParser from "body-parser";
|
||||
import { raw } from "body-parser";
|
||||
import type { Request, Response } from "express";
|
||||
|
||||
@Injectable()
|
||||
export class RawBodyMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: () => any) {
|
||||
bodyParser.raw({ type: "*/*" })(req, res, next);
|
||||
use(req: Request, res: Response, next: () => any): void {
|
||||
raw({ type: "*/*" })(req, res, next);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,30 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { PrismaReadService } from "@/modules/prisma/prisma-read.service";
|
||||
import { PrismaWriteService } from "@/modules/prisma/prisma-write.service";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { generateSecret } from "@calcom/platform-libraries";
|
||||
import type { Membership, Team, User } from "@calcom/prisma/client";
|
||||
import { AccessScope, OAuthClientType } from "@calcom/prisma/enums";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import * as nextAuthJwt from "next-auth/jwt";
|
||||
import * as request from "supertest";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuth2ClientRepositoryFixture } from "test/fixtures/repository/oauth2-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { generateSecret } from "@calcom/platform-libraries";
|
||||
import type { Membership, Team, User } from "@calcom/prisma/client";
|
||||
import { AccessScope, OAuthClientType } from "@calcom/prisma/enums";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
// Mock next-auth/jwt getToken for ApiAuthStrategy NEXT_AUTH authentication
|
||||
jest.mock("next-auth/jwt", () => ({
|
||||
getToken: jest.fn(),
|
||||
}));
|
||||
const mockGetToken = nextAuthJwt.getToken as jest.MockedFunction<typeof nextAuthJwt.getToken>;
|
||||
const mockGetToken = getToken as jest.MockedFunction<typeof getToken>;
|
||||
|
||||
describe("OAuth2 Controller Endpoints", () => {
|
||||
describe("User Not Authenticated", () => {
|
||||
@@ -140,12 +137,12 @@ describe("OAuth2 Controller Endpoints", () => {
|
||||
.get(`/api/v2/auth/oauth2/clients/${testClientId}`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.status).toBe("success");
|
||||
expect(response.body.data.id).toBe(testClientId);
|
||||
expect(response.body.data.name).toBe("Test OAuth Client");
|
||||
expect(response.body.data.redirectUri).toBe(testRedirectUri);
|
||||
expect(response.body.data.type).toBe(OAuthClientType.CONFIDENTIAL);
|
||||
expect(response.body.data.clientSecret).toBeUndefined();
|
||||
expect(response.body.status).toBe("success");
|
||||
expect(response.body.data.id).toBe(testClientId);
|
||||
expect(response.body.data.name).toBe("Test OAuth Client");
|
||||
expect(response.body.data.redirectUri).toBe(testRedirectUri);
|
||||
expect(response.body.data.type).toBe(OAuthClientType.CONFIDENTIAL);
|
||||
expect(response.body.data.clientSecret).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should return 404 for non-existent client ID", async () => {
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { CheckPlatformBillingResponseDto } from "@/modules/billing/controllers/outputs/CheckPlatformBillingResponse.dto";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import type { PlatformBilling, Team } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import Stripe from "stripe";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
@@ -18,8 +11,14 @@ import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repo
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import type { Team, PlatformBilling } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CheckPlatformBillingResponseDto } from "@/modules/billing/controllers/outputs/CheckPlatformBillingResponse.dto";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
|
||||
describe("Platform Billing Controller (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
@@ -126,7 +125,7 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
};
|
||||
},
|
||||
},
|
||||
} as unknown as Stripe)
|
||||
}) as unknown as Stripe
|
||||
);
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -170,7 +169,7 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
};
|
||||
},
|
||||
},
|
||||
} as unknown as Stripe)
|
||||
}) as unknown as Stripe
|
||||
);
|
||||
return request(app.getHttpServer())
|
||||
.post("/v2/billing/webhook")
|
||||
@@ -199,7 +198,7 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
};
|
||||
},
|
||||
},
|
||||
} as unknown as Stripe)
|
||||
}) as unknown as Stripe
|
||||
);
|
||||
|
||||
return request(app.getHttpServer())
|
||||
@@ -232,7 +231,7 @@ describe("Platform Billing Controller (e2e)", () => {
|
||||
};
|
||||
},
|
||||
},
|
||||
} as unknown as Stripe)
|
||||
}) as unknown as Stripe
|
||||
);
|
||||
|
||||
return request(app.getHttpServer())
|
||||
|
||||
+14
-15
@@ -1,18 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { ConferencingAppsOutputDto } from "@/modules/conferencing/outputs/get-conferencing-apps.output";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
GOOGLE_CALENDAR_ID,
|
||||
GOOGLE_CALENDAR_TYPE,
|
||||
@@ -22,6 +7,20 @@ import {
|
||||
} from "@calcom/platform-constants";
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { ConferencingAppsOutputDto } from "@/modules/conferencing/outputs/get-conferencing-apps.output";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("Conferencing Endpoints", () => {
|
||||
describe("conferencing controller e2e tests", () => {
|
||||
|
||||
+13
-15
@@ -1,5 +1,17 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { APPLE_CALENDAR_ID, APPLE_CALENDAR_TYPE, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { Credential, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CalendarsService } from "@/ee/calendars/services/calendars.service";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
@@ -7,20 +19,6 @@ import { PermissionsGuard } from "@/modules/auth/guards/permissions/permissions.
|
||||
import { DestinationCalendarsOutputResponseDto } from "@/modules/destination-calendars/outputs/destination-calendars.output";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { CredentialsRepositoryFixture } from "test/fixtures/repository/credentials.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { TokensRepositoryFixture } from "test/fixtures/repository/tokens.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { APPLE_CALENDAR_TYPE, APPLE_CALENDAR_ID } from "@calcom/platform-constants";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import type { PlatformOAuthClient, Team, User, Credential } from "@calcom/prisma/client";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:5555";
|
||||
|
||||
|
||||
+11
-12
@@ -1,5 +1,15 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import type { EventType, Webhook } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
@@ -10,17 +20,6 @@ import {
|
||||
EventTypeWebhooksOutputResponseDto,
|
||||
} from "@/modules/webhooks/outputs/event-type-webhook.output";
|
||||
import { DeleteManyWebhooksOutputResponseDto } from "@/modules/webhooks/outputs/webhook.output";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import type { EventType, Webhook } from "@calcom/prisma/client";
|
||||
|
||||
describe("EventTypes WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
+18
-19
@@ -1,5 +1,22 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { slugify } from "@calcom/platform-libraries";
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { DEFAULT_EVENT_TYPES } from "@/ee/event-types/event-types_2024_04_15/constants/constants";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
@@ -12,24 +29,6 @@ import { OAuthClientUsersService } from "@/modules/oauth-clients/services/oauth-
|
||||
import { CreateManagedUserInput } from "@/modules/users/inputs/create-managed-user.input";
|
||||
import { UpdateManagedUserInput } from "@/modules/users/inputs/update-managed-user.input";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { EventTypesRepositoryFixture } from "test/fixtures/repository/event-types.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { SchedulesRepositoryFixture } from "test/fixtures/repository/schedules.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { slugify } from "@calcom/platform-libraries";
|
||||
import type { ApiSuccessResponse } from "@calcom/platform-types";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
|
||||
const CLIENT_REDIRECT_URI = "http://localhost:4321";
|
||||
|
||||
|
||||
+13
-14
@@ -1,18 +1,8 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { CreateWebhookInputDto, UpdateWebhookInputDto } from "@/modules/webhooks/inputs/webhook.input";
|
||||
import {
|
||||
OAuthClientWebhooksOutputResponseDto,
|
||||
OAuthClientWebhookOutputResponseDto,
|
||||
} from "@/modules/webhooks/outputs/oauth-client-webhook.output";
|
||||
import type { PlatformOAuthClient, Team, Webhook } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import request from "supertest";
|
||||
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
@@ -22,8 +12,17 @@ import { UserRepositoryFixture } from "test/fixtures/repository/users.repository
|
||||
import { WebhookRepositoryFixture } from "test/fixtures/repository/webhooks.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import type { PlatformOAuthClient, Team, Webhook } from "@calcom/prisma/client";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { TokensModule } from "@/modules/tokens/tokens.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { CreateWebhookInputDto, UpdateWebhookInputDto } from "@/modules/webhooks/inputs/webhook.input";
|
||||
import {
|
||||
OAuthClientWebhookOutputResponseDto,
|
||||
OAuthClientWebhooksOutputResponseDto,
|
||||
} from "@/modules/webhooks/outputs/oauth-client-webhook.output";
|
||||
|
||||
describe("OAuth client WebhooksController (e2e)", () => {
|
||||
let app: INestApplication;
|
||||
|
||||
+22
-23
@@ -1,25 +1,3 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { ApiAuthStrategy } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { ApiAuthMockStrategy } from "test/mocks/api-auth-mock.strategy";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
|
||||
import {
|
||||
APPS_READ,
|
||||
APPS_WRITE,
|
||||
@@ -34,11 +12,32 @@ import {
|
||||
SUCCESS_STATUS,
|
||||
} from "@calcom/platform-constants";
|
||||
import type {
|
||||
ApiSuccessResponse,
|
||||
CreateOAuthClientInput,
|
||||
UpdateOAuthClientInput,
|
||||
ApiSuccessResponse,
|
||||
} from "@calcom/platform-types";
|
||||
import type { Membership, PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { ApiAuthMockStrategy } from "test/mocks/api-auth-mock.strategy";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { AuthModule } from "@/modules/auth/auth.module";
|
||||
import { ApiAuthStrategy } from "@/modules/auth/strategies/api-auth/api-auth.strategy";
|
||||
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
|
||||
describe("OAuth Clients Endpoints", () => {
|
||||
describe("User Not Authenticated", () => {
|
||||
|
||||
+14
-15
@@ -1,5 +1,18 @@
|
||||
import { bootstrap } from "@/app";
|
||||
import { X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { HttpExceptionFilter } from "@/filters/http-exception.filter";
|
||||
import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter";
|
||||
import { ZodExceptionFilter } from "@/filters/zod-exception.filter";
|
||||
@@ -10,20 +23,6 @@ import { ExchangeAuthorizationCodeInput } from "@/modules/oauth-clients/inputs/e
|
||||
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
|
||||
import { PrismaModule } from "@/modules/prisma/prisma.module";
|
||||
import { UsersModule } from "@/modules/users/users.module";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import { NestExpressApplication } from "@nestjs/platform-express";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import * as request from "supertest";
|
||||
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
|
||||
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
|
||||
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withNextAuth } from "test/utils/withNextAuth";
|
||||
|
||||
import { X_CAL_SECRET_KEY } from "@calcom/platform-constants";
|
||||
import type { PlatformOAuthClient, Team, User } from "@calcom/prisma/client";
|
||||
|
||||
describe("OAuthFlow Endpoints", () => {
|
||||
describe("User Not Authenticated", () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user