Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25d684ed3e | ||
|
|
195cb84a8b | ||
|
|
4466c4ad4b | ||
|
|
4b532646cb | ||
|
|
6d42fdb71c | ||
|
|
1b23671812 | ||
|
|
7d1fb7c659 | ||
|
|
97b99deb5d | ||
|
|
4803c956dc | ||
|
|
8ff5d476d9 |
No files matched your search
@@ -140,7 +140,3 @@ SENDGRID_SYNC_API_KEY=
|
|||||||
|
|
||||||
# Sendgrid internal email sender
|
# Sendgrid internal email sender
|
||||||
SENDGRID_API_KEY=
|
SENDGRID_API_KEY=
|
||||||
|
|
||||||
# Sentry
|
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
|
||||||
SENTRY_IGNORE_API_RESOLUTION_ERROR=
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# .github/workflows/preview.yml
|
||||||
|
|
||||||
|
name: Preview Environment
|
||||||
|
|
||||||
|
env:
|
||||||
|
SNAPLET_ACCESS_TOKEN: ${{ secrets.SNAPLET_ACCESS_TOKEN }}
|
||||||
|
SNAPLET_PROJECT_ID: ${{ secrets.SNAPLET_PROJECT_ID }}
|
||||||
|
VERCEL_ACCESS_TOKEN: ${{ secrets.VERCEL_ACCESS_TOKEN }}
|
||||||
|
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, closed]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
db-preview-deploy:
|
||||||
|
if: ${{ github.event.action == 'opened' || github.event.action == 'synchronize' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- id: snaplet
|
||||||
|
uses: snaplet/action@v1
|
||||||
|
- uses: snaplet/vercel-action@v1
|
||||||
|
with:
|
||||||
|
env: |
|
||||||
|
DATABASE_URL=${{ steps.snaplet.outputs.database-url }}
|
||||||
|
db-preview-delete:
|
||||||
|
if: ${{ github.event.action == 'closed' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: snaplet/action@v1
|
||||||
|
with:
|
||||||
|
delete: true
|
||||||
|
- uses: snaplet/vercel-action@v1
|
||||||
|
with:
|
||||||
|
delete: true
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
# Pattern matched against refs/tags
|
||||||
|
tags:
|
||||||
|
- "*" # Push events to every tag not containing /
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: curl -X POST -d {} ${{ secrets.VERCEL_VERSIONED_DEPLOY_HOOK }}
|
||||||
+1
-1
Submodule apps/api updated: e0619d383a...0f5017010f.
+1
-3
@@ -67,6 +67,4 @@ tsconfig.tsbuildinfo
|
|||||||
public/embed
|
public/embed
|
||||||
|
|
||||||
# Copied app-store images
|
# Copied app-store images
|
||||||
public/app-store
|
public/app-store
|
||||||
# Sentry
|
|
||||||
.sentryclirc
|
|
||||||
@@ -57,10 +57,13 @@ const goto = (router: NextRouter, searchParams: Record<string, string>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const removeQueryParams = (router: NextRouter, queryParams: string[]) => {
|
const removeQueryParams = (router: NextRouter, queryParams: string[]) => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
queryParams.forEach((param) => {
|
queryParams.forEach((param) => {
|
||||||
delete router.query[param];
|
params.delete(param);
|
||||||
});
|
});
|
||||||
router.push(`${router.asPath.split("?")[0]}?${router.query.toString()}`);
|
|
||||||
|
router.push(`${router.asPath.split("?")[0]}?${params.toString()}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,10 +3,18 @@ import { AppsStatus } from "@calcom/types/Calendar";
|
|||||||
import * as fetch from "@lib/core/http/fetch-wrapper";
|
import * as fetch from "@lib/core/http/fetch-wrapper";
|
||||||
import { BookingCreateBody, BookingResponse } from "@lib/types/booking";
|
import { BookingCreateBody, BookingResponse } from "@lib/types/booking";
|
||||||
|
|
||||||
type ExtendedBookingCreateBody = BookingCreateBody & { noEmail?: boolean; recurringCount?: number };
|
type ExtendedBookingCreateBody = BookingCreateBody & {
|
||||||
|
noEmail?: boolean;
|
||||||
|
recurringCount?: number;
|
||||||
|
appsStatus?: AppsStatus[] | undefined;
|
||||||
|
allRecurringDates?: string[];
|
||||||
|
currentRecurringIndex?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
|
const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
|
||||||
const createdBookings: BookingResponse[] = [];
|
const createdBookings: BookingResponse[] = [];
|
||||||
|
const allRecurringDates: string[] = data.map((booking) => booking.start);
|
||||||
|
let appsStatus: AppsStatus[] | undefined = undefined;
|
||||||
// Reversing to accumulate results for noEmail instances first, to then lastly, create the
|
// Reversing to accumulate results for noEmail instances first, to then lastly, create the
|
||||||
// emailed booking taking into account accumulated results to send app status accurately
|
// emailed booking taking into account accumulated results to send app status accurately
|
||||||
for (let key = 0; key < data.length; key++) {
|
for (let key = 0; key < data.length; key++) {
|
||||||
@@ -23,22 +31,16 @@ const createRecurringBooking = async (data: ExtendedBookingCreateBody[]) => {
|
|||||||
}
|
}
|
||||||
return prev;
|
return prev;
|
||||||
}, {} as { [key: string]: AppsStatus });
|
}, {} as { [key: string]: AppsStatus });
|
||||||
const appsStatus = Object.values(calcAppsStatus);
|
appsStatus = Object.values(calcAppsStatus);
|
||||||
const response = await fetch.post<
|
|
||||||
ExtendedBookingCreateBody & { appsStatus: AppsStatus[] },
|
|
||||||
BookingResponse
|
|
||||||
>("/api/book/event", {
|
|
||||||
...booking,
|
|
||||||
appsStatus,
|
|
||||||
});
|
|
||||||
createdBookings.push(response);
|
|
||||||
} else {
|
|
||||||
const response = await fetch.post<ExtendedBookingCreateBody, BookingResponse>("/api/book/event", {
|
|
||||||
...booking,
|
|
||||||
noEmail: true,
|
|
||||||
});
|
|
||||||
createdBookings.push(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = await fetch.post<ExtendedBookingCreateBody, BookingResponse>("/api/book/event", {
|
||||||
|
...booking,
|
||||||
|
appsStatus,
|
||||||
|
allRecurringDates,
|
||||||
|
currentRecurringIndex: key,
|
||||||
|
});
|
||||||
|
createdBookings.push(response);
|
||||||
}
|
}
|
||||||
return createdBookings;
|
return createdBookings;
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-14
@@ -1,6 +1,5 @@
|
|||||||
require("dotenv").config({ path: "../../.env" });
|
require("dotenv").config({ path: "../../.env" });
|
||||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||||
const { withSentryConfig } = require("@sentry/nextjs");
|
|
||||||
|
|
||||||
const withTM = require("next-transpile-modules")([
|
const withTM = require("next-transpile-modules")([
|
||||||
"@calcom/app-store",
|
"@calcom/app-store",
|
||||||
@@ -230,18 +229,6 @@ const nextConfig = {
|
|||||||
|
|
||||||
return redirects;
|
return redirects;
|
||||||
},
|
},
|
||||||
sentry: {
|
|
||||||
hideSourceMaps: true,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const sentryWebpackPluginOptions = {
|
module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
|
||||||
silent: true, // Suppresses all logs
|
|
||||||
};
|
|
||||||
|
|
||||||
const moduleExports = () => plugins.reduce((acc, next) => next(acc), nextConfig);
|
|
||||||
|
|
||||||
// Sentry should be the last thing to export to catch everything right
|
|
||||||
module.exports = process.env.NEXT_PUBLIC_SENTRY_DSN
|
|
||||||
? withSentryConfig(moduleExports, sentryWebpackPluginOptions)
|
|
||||||
: moduleExports;
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.1.3",
|
"version": "2.1.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -56,7 +56,6 @@
|
|||||||
"@radix-ui/react-switch": "^1.0.0",
|
"@radix-ui/react-switch": "^1.0.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.0.0",
|
"@radix-ui/react-toggle-group": "^1.0.0",
|
||||||
"@radix-ui/react-tooltip": "^1.0.0",
|
"@radix-ui/react-tooltip": "^1.0.0",
|
||||||
"@sentry/nextjs": "^7.17.3",
|
|
||||||
"@stripe/react-stripe-js": "^1.10.0",
|
"@stripe/react-stripe-js": "^1.10.0",
|
||||||
"@stripe/stripe-js": "^1.35.0",
|
"@stripe/stripe-js": "^1.35.0",
|
||||||
"@tanstack/react-query": "^4.3.9",
|
"@tanstack/react-query": "^4.3.9",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* Typescript class based component for custom-error
|
* Typescript class based component for custom-error
|
||||||
* @link https://nextjs.org/docs/advanced-features/custom-error-page
|
* @link https://nextjs.org/docs/advanced-features/custom-error-page
|
||||||
*/
|
*/
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
import { NextPage, NextPageContext } from "next";
|
import { NextPage, NextPageContext } from "next";
|
||||||
import NextError, { ErrorProps } from "next/error";
|
import NextError, { ErrorProps } from "next/error";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -47,9 +46,7 @@ const CustomError: NextPage<CustomErrorProps> = (props) => {
|
|||||||
* Partially adapted from the example in
|
* Partially adapted from the example in
|
||||||
* https://github.com/vercel/next.js/tree/canary/examples/with-sentry
|
* https://github.com/vercel/next.js/tree/canary/examples/with-sentry
|
||||||
*/
|
*/
|
||||||
CustomError.getInitialProps = async (ctx: AugmentedNextPageContext) => {
|
CustomError.getInitialProps = async ({ res, err, asPath }: AugmentedNextPageContext) => {
|
||||||
const { res, err, asPath } = ctx;
|
|
||||||
await Sentry.captureUnderscoreErrorException(ctx);
|
|
||||||
const errorInitialProps = (await NextError.getInitialProps({
|
const errorInitialProps = (await NextError.getInitialProps({
|
||||||
res,
|
res,
|
||||||
err,
|
err,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import path from "path";
|
|||||||
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
import checkLicense from "@calcom/features/ee/common/server/checkLicense";
|
||||||
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
import ImpersonationProvider from "@calcom/features/ee/impersonation/lib/ImpersonationProvider";
|
||||||
import { hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
import { hostedCal, isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
|
||||||
|
import { ErrorCode, verifyPassword, isPasswordValid } from "@calcom/lib/auth";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
import { symmetricDecrypt } from "@calcom/lib/crypto";
|
||||||
import { defaultCookies } from "@calcom/lib/default-cookies";
|
import { defaultCookies } from "@calcom/lib/default-cookies";
|
||||||
@@ -20,7 +21,6 @@ import rateLimit from "@calcom/lib/rateLimit";
|
|||||||
import { serverConfig } from "@calcom/lib/serverConfig";
|
import { serverConfig } from "@calcom/lib/serverConfig";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { ErrorCode, verifyPassword } from "@lib/auth";
|
|
||||||
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
|
import CalComAdapter from "@lib/auth/next-auth-custom-adapter";
|
||||||
import { randomString } from "@lib/random";
|
import { randomString } from "@lib/random";
|
||||||
import slugify from "@lib/slugify";
|
import slugify from "@lib/slugify";
|
||||||
@@ -53,6 +53,17 @@ const providers: Provider[] = [
|
|||||||
where: {
|
where: {
|
||||||
email: credentials.email.toLowerCase(),
|
email: credentials.email.toLowerCase(),
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
role: true,
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
identityProvider: true,
|
||||||
|
password: true,
|
||||||
|
twoFactorEnabled: true,
|
||||||
|
twoFactorSecret: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -106,6 +117,17 @@ const providers: Provider[] = [
|
|||||||
});
|
});
|
||||||
await limiter.check(10, user.email); // 10 requests per minute
|
await limiter.check(10, user.email); // 10 requests per minute
|
||||||
|
|
||||||
|
// authentication success- but does it meet the minimum password requirements?
|
||||||
|
if (user.role === "ADMIN" && !isPasswordValid(credentials.password, false, true)) {
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
name: user.name,
|
||||||
|
role: "USER",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -218,6 +240,13 @@ export default NextAuth({
|
|||||||
const existingUser = await prisma.user.findFirst({
|
const existingUser = await prisma.user.findFirst({
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
where: { email: token.email! },
|
where: { email: token.email! },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
username: true,
|
||||||
|
name: true,
|
||||||
|
email: true,
|
||||||
|
role: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!existingUser) {
|
if (!existingUser) {
|
||||||
@@ -225,20 +254,18 @@ export default NextAuth({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: existingUser.id,
|
...existingUser,
|
||||||
username: existingUser.username,
|
...token,
|
||||||
name: existingUser.name,
|
|
||||||
email: existingUser.email,
|
|
||||||
role: existingUser.role,
|
|
||||||
impersonatedByUID: token?.impersonatedByUID as number,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return await autoMergeIdentities();
|
return await autoMergeIdentities();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account && account.type === "credentials") {
|
if (account && account.type === "credentials") {
|
||||||
return {
|
return {
|
||||||
|
...token,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -273,6 +300,7 @@ export default NextAuth({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...token,
|
||||||
id: existingUser.id,
|
id: existingUser.id,
|
||||||
name: existingUser.name,
|
name: existingUser.name,
|
||||||
username: existingUser.username,
|
username: existingUser.username,
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ const querySchema = z.object({
|
|||||||
.refine((val) => val.trim().length >= 1, { message: "Please enter at least one character" }),
|
.refine((val) => val.trim().length >= 1, { message: "Please enter at least one character" }),
|
||||||
full_name: z.string().min(3, "Please enter at least 3 characters"),
|
full_name: z.string().min(3, "Please enter at least 3 characters"),
|
||||||
email_address: z.string().email({ message: "Please enter a valid email" }),
|
email_address: z.string().email({ message: "Please enter a valid email" }),
|
||||||
password: z.string().refine((val) => isPasswordValid(val.trim()), {
|
password: z.string().refine((val) => isPasswordValid(val.trim(), false, true), {
|
||||||
message:
|
message:
|
||||||
"The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
|
"The password must be a minimum of 15 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
async function handler(req: NextApiRequest, res: NextApiResponse) {
|
async function handler(req: NextApiRequest) {
|
||||||
const userCount = await prisma.user.count();
|
const userCount = await prisma.user.count();
|
||||||
if (userCount !== 0) {
|
if (userCount !== 0) {
|
||||||
throw new HttpError({ statusCode: 400, message: "No setup needed." });
|
throw new HttpError({ statusCode: 400, message: "No setup needed." });
|
||||||
|
|||||||
@@ -126,6 +126,7 @@
|
|||||||
"webhook_updated_successfully": "Webhook updated successfully!",
|
"webhook_updated_successfully": "Webhook updated successfully!",
|
||||||
"webhook_removed_successfully": "Webhook removed successfully!",
|
"webhook_removed_successfully": "Webhook removed successfully!",
|
||||||
"payload_template": "Payload Template",
|
"payload_template": "Payload Template",
|
||||||
|
"payload":"Payload",
|
||||||
"dismiss": "Dismiss",
|
"dismiss": "Dismiss",
|
||||||
"no_data_yet": "No data yet",
|
"no_data_yet": "No data yet",
|
||||||
"ping_test": "Ping test",
|
"ping_test": "Ping test",
|
||||||
@@ -417,7 +418,7 @@
|
|||||||
"forgotten_secret_description": "If you have lost or forgotten this secret, you can change it, but be aware that all integrations using this secret will need to be updated",
|
"forgotten_secret_description": "If you have lost or forgotten this secret, you can change it, but be aware that all integrations using this secret will need to be updated",
|
||||||
"current_incorrect_password": "Current password is incorrect",
|
"current_incorrect_password": "Current password is incorrect",
|
||||||
"password_hint_caplow": "Mix of uppercase & lowercase letters",
|
"password_hint_caplow": "Mix of uppercase & lowercase letters",
|
||||||
"password_hint_min": "Minimum 7 characters long",
|
"password_hint_min": "Minimum 8 characters long",
|
||||||
"password_hint_num": "Contain at least 1 number",
|
"password_hint_num": "Contain at least 1 number",
|
||||||
"invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
|
"invalid_password_hint": "The password must be a minimum of 7 characters long containing at least one number and have a mixture of uppercase and lowercase letters",
|
||||||
"incorrect_password": "Password is incorrect.",
|
"incorrect_password": "Password is incorrect.",
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
// This file configures the initialization of Sentry on the browser.
|
|
||||||
// The config you add here will be used whenever a page is visited.
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
// Adjust this value in production, or use tracesSampler for greater control
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
// ...
|
|
||||||
// Note: if you want to override the automatic release value, do not set a
|
|
||||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
|
||||||
// that it will also get attached to your source maps
|
|
||||||
});
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
defaults.url=https://sentry.io/
|
|
||||||
defaults.org=calcom
|
|
||||||
defaults.project=cal
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
// This file configures the initialization of Sentry on the server.
|
|
||||||
// The config you add here will be used whenever the server handles a request.
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
|
||||||
|
|
||||||
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: SENTRY_DSN,
|
|
||||||
// Adjust this value in production, or use tracesSampler for greater control
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
// ...
|
|
||||||
// Note: if you want to override the automatic release value, do not set a
|
|
||||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
|
||||||
// that it will also get attached to your source maps
|
|
||||||
});
|
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
import async from "async";
|
import async from "async";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
import { RRule } from "rrule";
|
|
||||||
import short from "short-uuid";
|
import short from "short-uuid";
|
||||||
import { v5 as uuidv5 } from "uuid";
|
import { v5 as uuidv5 } from "uuid";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
@@ -212,7 +211,11 @@ async function ensureAvailableUsers(
|
|||||||
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> & {
|
eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> & {
|
||||||
users: User[];
|
users: User[];
|
||||||
},
|
},
|
||||||
input: { dateFrom: string; dateTo: string }
|
input: { dateFrom: string; dateTo: string },
|
||||||
|
recurringDatesInfo?: {
|
||||||
|
allRecurringDates: string[] | undefined;
|
||||||
|
currentRecurringIndex: number | undefined;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
const availableUsers: typeof eventType.users = [];
|
const availableUsers: typeof eventType.users = [];
|
||||||
/** Let's start checking for availability */
|
/** Let's start checking for availability */
|
||||||
@@ -243,9 +246,12 @@ async function ensureAvailableUsers(
|
|||||||
|
|
||||||
let foundConflict = false;
|
let foundConflict = false;
|
||||||
try {
|
try {
|
||||||
if (eventType.recurringEvent) {
|
if (
|
||||||
const recurringEvent = parseRecurringEvent(eventType.recurringEvent);
|
eventType.recurringEvent &&
|
||||||
const allBookingDates = new RRule({ dtstart: new Date(input.dateFrom), ...recurringEvent }).all();
|
recurringDatesInfo?.currentRecurringIndex === 0 &&
|
||||||
|
recurringDatesInfo.allRecurringDates
|
||||||
|
) {
|
||||||
|
const allBookingDates = recurringDatesInfo.allRecurringDates.map((strDate) => new Date(strDate));
|
||||||
// Go through each date for the recurring event and check if each one's availability
|
// Go through each date for the recurring event and check if each one's availability
|
||||||
// DONE: Decreased computational complexity from O(2^n) to O(n) by refactoring this loop to stop
|
// DONE: Decreased computational complexity from O(2^n) to O(n) by refactoring this loop to stop
|
||||||
// running at the first unavailable time.
|
// running at the first unavailable time.
|
||||||
@@ -277,6 +283,8 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
recurringCount,
|
recurringCount,
|
||||||
|
allRecurringDates,
|
||||||
|
currentRecurringIndex,
|
||||||
noEmail,
|
noEmail,
|
||||||
eventTypeSlug,
|
eventTypeSlug,
|
||||||
eventTypeId,
|
eventTypeId,
|
||||||
@@ -376,10 +384,20 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
|||||||
{
|
{
|
||||||
...eventType,
|
...eventType,
|
||||||
users,
|
users,
|
||||||
|
...(eventType.recurringEvent && {
|
||||||
|
recurringEvent: {
|
||||||
|
...eventType.recurringEvent,
|
||||||
|
count: recurringCount || eventType.recurringEvent.count,
|
||||||
|
},
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dateFrom: reqBody.start,
|
dateFrom: reqBody.start,
|
||||||
dateTo: reqBody.end,
|
dateTo: reqBody.end,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
allRecurringDates,
|
||||||
|
currentRecurringIndex,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ const WebhookForm = (props: {
|
|||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<>
|
<>
|
||||||
<Label className="font-sm mt-8 text-gray-900">
|
<Label className="font-sm mt-8 text-gray-900">
|
||||||
<>{t("event_triggers")}</>
|
<>{t("payload")}</>
|
||||||
</Label>
|
</Label>
|
||||||
<div className="flex rounded-md border">
|
<div className="flex rounded-md border">
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -513,7 +513,6 @@ export default class CloseCom {
|
|||||||
description: "Generic Lead for Contacts created by Cal.com",
|
description: "Generic Lead for Contacts created by Cal.com",
|
||||||
}
|
}
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
debugger;
|
|
||||||
const closeComLeadNames = await this.lead.list({ query: { _fields: ["name", "id"] } });
|
const closeComLeadNames = await this.lead.list({ query: { _fields: ["name", "id"] } });
|
||||||
const searchLeadFromCalCom = closeComLeadNames.data.filter((lead) => lead.name === leadInfo.companyName);
|
const searchLeadFromCalCom = closeComLeadNames.data.filter((lead) => lead.name === leadInfo.companyName);
|
||||||
if (searchLeadFromCalCom.length === 0) {
|
if (searchLeadFromCalCom.length === 0) {
|
||||||
|
|||||||
@@ -36,14 +36,15 @@ export async function getSession(options: GetSessionParams): Promise<Session | n
|
|||||||
export function isPasswordValid(password: string): boolean;
|
export function isPasswordValid(password: string): boolean;
|
||||||
export function isPasswordValid(
|
export function isPasswordValid(
|
||||||
password: string,
|
password: string,
|
||||||
breakdown: boolean
|
breakdown: boolean,
|
||||||
|
strict?: boolean
|
||||||
): { caplow: boolean; num: boolean; min: boolean };
|
): { caplow: boolean; num: boolean; min: boolean };
|
||||||
export function isPasswordValid(password: string, breakdown?: boolean) {
|
export function isPasswordValid(password: string, breakdown?: boolean, strict?: boolean) {
|
||||||
let cap = false, // Has uppercase characters
|
let cap = false, // Has uppercase characters
|
||||||
low = false, // Has lowercase characters
|
low = false, // Has lowercase characters
|
||||||
num = false, // At least one number
|
num = false, // At least one number
|
||||||
min = false; // Seven characters
|
min = false; // Eight characters, or fifteen in strict mode.
|
||||||
if (password.length > 6) min = true;
|
if (password.length > 7 && (!strict || password.length > 14)) min = true;
|
||||||
for (let i = 0; i < password.length; i++) {
|
for (let i = 0; i < password.length; i++) {
|
||||||
if (!isNaN(parseInt(password[i]))) num = true;
|
if (!isNaN(parseInt(password[i]))) num = true;
|
||||||
else {
|
else {
|
||||||
|
|||||||
@@ -150,6 +150,8 @@ export const extendedBookingCreateBody = bookingCreateBodySchema.merge(
|
|||||||
z.object({
|
z.object({
|
||||||
noEmail: z.boolean().optional(),
|
noEmail: z.boolean().optional(),
|
||||||
recurringCount: z.number().optional(),
|
recurringCount: z.number().optional(),
|
||||||
|
allRecurringDates: z.string().array().optional(),
|
||||||
|
currentRecurringIndex: z.number().optional(),
|
||||||
rescheduleReason: z.string().optional(),
|
rescheduleReason: z.string().optional(),
|
||||||
smsReminderNumber: z.string().optional(),
|
smsReminderNumber: z.string().optional(),
|
||||||
appsStatus: z
|
appsStatus: z
|
||||||
|
|||||||
@@ -229,8 +229,6 @@
|
|||||||
"$SAML_ADMINS",
|
"$SAML_ADMINS",
|
||||||
"$SAML_DATABASE_URL",
|
"$SAML_DATABASE_URL",
|
||||||
"$SEND_FEEDBACK_EMAIL",
|
"$SEND_FEEDBACK_EMAIL",
|
||||||
"$SENTRY_DSN",
|
|
||||||
"$NEXT_PUBLIC_SENTRY_DSN",
|
|
||||||
"$SLACK_CLIENT_ID",
|
"$SLACK_CLIENT_ID",
|
||||||
"$SLACK_CLIENT_SECRET",
|
"$SLACK_CLIENT_SECRET",
|
||||||
"$SLACK_SIGNING_SECRET",
|
"$SLACK_SIGNING_SECRET",
|
||||||
|
|||||||
Reference in new issue
Block a user