Stripe to monorepo (#2063)

* downgrade func

* fix security hole lol

* fix query conditions

* - set to trial not free
- auto create stripe customer if missing
- fix production check

* Extracts downgrade logic to script, fixes ts-node conflicts with prisma

* Adds trialEndsAt field to users

* Updates trial/downgrade logic

* Typo

* Legibility fixes

* Update team-billing.ts

* Legibility improvements

* Updates illegal logic

* WIP

* WIP migrating stripe to package

* Update website

* Import fixes

* Import fixes

* Fixes to downgrade script

* Check for premium usernames before downgrading

* Fixed formatting

* Delete deploy-env.sh

* Locks dayjs to 1.10.6

* Type fixes

* Seems like we're stuck with dayjs 1.10.4

* Script fixes

* Adds first name to dump

* Loop fix

Co-authored-by: Jamie <ijamespine@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Omar López 2022-03-09 15:56:05 -07:00 committed by GitHub
parent adbae64619
commit 5625cf226b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 454 additions and 405 deletions

View file

@ -12,7 +12,7 @@ import { ReactMultiEmail } from "react-multi-email";
import { useMutation } from "react-query";
import { v4 as uuidv4 } from "uuid";
import { createPaymentLink } from "@ee/lib/stripe/client";
import { createPaymentLink } from "@calcom/stripe/client";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";

View file

@ -4,7 +4,7 @@ import { useRouter } from "next/router";
import { stringify } from "querystring";
import React, { SyntheticEvent, useEffect, useState } from "react";
import { PaymentData } from "@ee/lib/stripe/server";
import { PaymentData } from "@calcom/stripe/server";
import { useLocale } from "@lib/hooks/useLocale";

View file

@ -8,8 +8,8 @@ import Head from "next/head";
import React, { FC, useEffect, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
import getStripe from "@calcom/stripe/client";
import PaymentComponent from "@ee/components/stripe/Payment";
import getStripe from "@ee/lib/stripe/client";
import { PaymentPageProps } from "@ee/pages/payment/[uid]";
import { useLocale } from "@lib/hooks/useLocale";

View file

@ -1,90 +0,0 @@
#!/usr/bin/env ts-node
// To run this script: `yarn downgrade 2>&1 | tee result.log`
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
import dayjs from "dayjs";
import prisma from "@calcom/prisma";
import { TRIAL_LIMIT_DAYS } from "@lib/config/constants";
import { getStripeCustomerIdFromUserId } from "./customer";
import stripe from "./server";
import { getPremiumPlanPrice, getProPlanPrice } from "./team-billing";
export async function downgradeIllegalProUsers() {
const usersDowngraded: string[] = [];
const illegalProUsers = await prisma.membership.findMany({
where: {
role: {
not: MembershipRole.OWNER,
},
user: {
plan: {
not: UserPlan.PRO,
},
},
},
include: {
user: true,
},
});
const downgrade = async (member: typeof illegalProUsers[number]) => {
console.log(`Downgrading: ${member.user.email}`);
await prisma.user.update({
where: { id: member.user.id },
data: {
plan: UserPlan.TRIAL,
trialEndsAt: dayjs().add(TRIAL_LIMIT_DAYS, "day").toDate(),
},
});
console.log(`Downgraded: ${member.user.email}`);
usersDowngraded.push(member.user.username || `${member.user.id}`);
};
for (const member of illegalProUsers) {
const metadata = (member.user.metadata as Prisma.JsonObject) ?? {};
// if their pro is already sponsored by a team, do not downgrade
if (metadata.proPaidForTeamId !== undefined) continue;
const stripeCustomerId = await getStripeCustomerIdFromUserId(member.user.id);
if (!stripeCustomerId) {
await downgrade(member);
continue;
}
const customer = await stripe.customers.retrieve(stripeCustomerId, {
expand: ["subscriptions.data.plan"],
});
if (!customer || customer.deleted) {
await downgrade(member);
continue;
}
const subscription = customer.subscriptions?.data[0];
if (!subscription) {
await downgrade(member);
continue;
}
const hasProPlan = !!subscription.items.data.find(
(item) => item.plan.id === getProPlanPrice() || item.plan.id === getPremiumPlanPrice()
);
// if they're pro, do not downgrade
if (hasProPlan) continue;
await downgrade(member);
}
return {
usersDowngraded,
usersDowngradedAmount: usersDowngraded.length,
};
}
downgradeIllegalProUsers()
.then(({ usersDowngraded, usersDowngradedAmount }) => {
console.log(`Downgraded ${usersDowngradedAmount} illegal pro users`);
console.table(usersDowngraded);
})
.catch((e) => {
console.error(e);
process.exit(1);
});

View file

@ -3,36 +3,22 @@ import Stripe from "stripe";
import { v4 as uuidv4 } from "uuid";
import prisma from "@calcom/prisma";
import { createPaymentLink } from "@calcom/stripe/client";
import stripe, { PaymentData } from "@calcom/stripe/server";
import { sendAwaitingPaymentEmail, sendOrganizerPaymentRefundFailedEmail } from "@lib/emails/email-manager";
import { getErrorFromUnknown } from "@lib/errors";
import { CalendarEvent } from "@lib/integrations/calendar/interfaces/Calendar";
import { createPaymentLink } from "./client";
export type PaymentInfo = {
link?: string | null;
reason?: string | null;
id?: string | null;
};
export type PaymentData = Stripe.Response<Stripe.PaymentIntent> & {
stripe_publishable_key: string;
stripeAccount: string;
};
export type StripeData = Stripe.OAuthToken & {
default_currency: string;
};
const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!;
const paymentFeePercentage = process.env.PAYMENT_FEE_PERCENTAGE!;
const paymentFeeFixed = process.env.PAYMENT_FEE_FIXED!;
const stripe = new Stripe(stripePrivateKey, {
apiVersion: "2020-08-27",
});
export async function handlePayment(
evt: CalendarEvent,
selectedEventType: {
@ -168,5 +154,3 @@ async function handleRefundError(opts: { event: CalendarEvent; reason: string; p
paymentInfo: { reason: opts.reason, id: opts.paymentId },
});
}
export default stripe;

View file

@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { stringify } from "querystring";
import stripe, { StripeData } from "@ee/lib/stripe/server";
import stripe, { StripeData } from "@calcom/stripe/server";
import { getSession } from "@lib/auth";
import prisma from "@lib/prisma";

View file

@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { getStripeCustomerIdFromUserId } from "@ee/lib/stripe/customer";
import stripe from "@ee/lib/stripe/server";
import { getStripeCustomerIdFromUserId } from "@calcom/stripe/customer";
import stripe from "@calcom/stripe/server";
import { getSession } from "@lib/auth";

View file

@ -2,7 +2,7 @@ import { buffer } from "micro";
import type { NextApiRequest, NextApiResponse } from "next";
import Stripe from "stripe";
import stripe from "@ee/lib/stripe/server";
import stripe from "@calcom/stripe/server";
import { IS_PRODUCTION } from "@lib/config/constants";
import { HttpError as HttpCode } from "@lib/core/http/error";

View file

@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { upgradeTeam } from "@ee/lib/stripe/team-billing";
import { upgradeTeam } from "@calcom/stripe/team-billing";
import { getSession } from "@lib/auth";

View file

@ -1,6 +1,6 @@
import { GetServerSidePropsContext } from "next";
import { PaymentData } from "@ee/lib/stripe/server";
import { PaymentData } from "@calcom/stripe/server";
import { asStringOrThrow } from "@lib/asStringOrNull";
import prisma from "@lib/prisma";

View file

@ -1,4 +1,10 @@
const withTM = require("next-transpile-modules")(["@calcom/lib", "@calcom/prisma", "@calcom/ui"]);
const withTM = require("next-transpile-modules")([
"@calcom/ee",
"@calcom/lib",
"@calcom/prisma",
"@calcom/stripe",
"@calcom/ui",
]);
const { i18n } = require("./next-i18next.config");
// So we can test deploy previews preview

View file

@ -19,8 +19,7 @@
"lint": "next lint",
"lint:report": "eslint . --format json --output-file ../../lint-results/web.json",
"lint:fix": "next lint . --ext .ts,.js,.tsx,.jsx --fix",
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts",
"downgrade": "ts-node ee/lib/stripe/downgrade.ts"
"check-changed-files": "ts-node scripts/ts-check-changed-files.ts"
},
"engines": {
"node": ">=14.x",
@ -28,8 +27,10 @@
},
"dependencies": {
"@boxyhq/saml-jackson": "0.3.6",
"@calcom/ee": "*",
"@calcom/lib": "*",
"@calcom/prisma": "*",
"@calcom/stripe": "*",
"@calcom/tsconfig": "*",
"@calcom/ui": "*",
"@daily-co/daily-js": "^0.21.0",
@ -61,7 +62,7 @@
"async": "^3.2.1",
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dayjs": "^1.10.6",
"dayjs": "^1.10.4",
"dayjs-business-time": "^1.0.4",
"googleapis": "^84.0.0",
"handlebars": "^4.7.7",

View file

@ -1,7 +1,7 @@
import { Prisma } from "@prisma/client";
import type { NextApiRequest, NextApiResponse } from "next";
import { getStripeCustomerId } from "@ee/lib/stripe/customer";
import { getStripeCustomerId } from "@calcom/stripe/customer";
import { getSession } from "@lib/auth";
import { WEBSITE_URL } from "@lib/config/constants";

View file

@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { deleteStripeCustomer } from "@ee/lib/stripe/customer";
import { deleteStripeCustomer } from "@calcom/stripe/customer";
import { getSession } from "@lib/auth";
import prisma from "@lib/prisma";

View file

@ -27,7 +27,7 @@ import { FormattedNumber, IntlProvider } from "react-intl";
import Select from "react-select";
import { JSONObject } from "superjson/dist/types";
import { StripeData } from "@ee/lib/stripe/server";
import { StripeData } from "@calcom/stripe/server";
import { asStringOrThrow, asStringOrUndefined } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";

View file

@ -3,7 +3,7 @@ import _ from "lodash";
import { JSONObject } from "superjson/dist/types";
import { z } from "zod";
import { checkPremiumUsername } from "@ee/lib/core/checkPremiumUsername";
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
import { getCalendarCredentials, getConnectedCalendars } from "@lib/integrations/calendar/CalendarManager";

View file

@ -10,7 +10,7 @@ import {
downgradeTeamMembers,
upgradeTeam,
ensureSubscriptionQuantityCorrectness,
} from "@ee/lib/stripe/team-billing";
} from "@calcom/stripe/team-billing";
import { BASE_URL } from "@lib/config/constants";
import { HOSTED_CAL_FEATURES } from "@lib/config/constants";

@ -1 +1 @@
Subproject commit 59cd13be0a9dc9e3bfc7b9acf774e6ac292f57db
Subproject commit 6545bb5cf0d5e38dcab9cce47853b7a7a4859033

42
packages/ee/LICENSE Normal file
View file

@ -0,0 +1,42 @@
The Cal.com Enterprise Edition (EE) license (the “EE License”)
Copyright (c) 2020-present Cal.com, Inc
With regard to the Cal.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cal.com Subscription Terms available
at https://cal.com/terms (the “EE Terms”), or other agreements governing
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription")
for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid EE Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This EE License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this EE License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Cal.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.

38
packages/ee/README.md Normal file
View file

@ -0,0 +1,38 @@
<!-- PROJECT LOGO -->
<div align="center">
<a href="https://cal.com/enterprise">
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
</a>
<a href="https://cal.com/enterprise">Get Started with Enterprise</a>
</div>
# Enterprise Edition
Welcome to the Enterprise Edition ("/ee") of Cal.com.
The [/ee](https://github.com/calcom/cal.com/tree/main/apps/web/ee) subfolder is the place for all the **Pro** features from our [hosted](https://cal.com/pricing) plan and [enterprise-grade](https://cal.com/enterprise) features such as SSO, SAML, ADFS, OIDC, SCIM, SIEM, HRIS and much more.
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calcom/cal.com)). You are not allowed to use this code to host your own version of app.cal.com without obtaining a proper [license](https://cal.com/enterprise) first❗_
## Setting up Stripe
1. Create a stripe account or use an existing one. For testing, you should use all stripe dashboard functions with the Test-Mode toggle in the top right activated.
2. Open [Stripe ApiKeys](https://dashboard.stripe.com/apikeys) save the token starting with `pk_...` to `NEXT_PUBLIC_STRIPE_PUBLIC_KEY` and `sk_...` to `STRIPE_PRIVATE_KEY` in the .env file.
3. Open [Stripe Connect Settings](https://dashboard.stripe.com/settings/connect) and activate OAuth for Standard Accounts
4. Add `<CALENDSO URL>/api/integrations/stripepayment/callback` as redirect URL.
5. Copy your client*id (`ca*...`) to `STRIPE_CLIENT_ID` in the .env file.
6. Open [Stripe Webhooks](https://dashboard.stripe.com/webhooks) and add `<CALENDSO URL>/api/integrations/stripepayment/webhook` as webhook for connected applications.
7. Select all `payment_intent` events for the webhook.
8. Copy the webhook secret (`whsec_...`) to `STRIPE_WEBHOOK_SECRET` in the .env file.
## Setting up SAML login
1. Set SAML_DATABASE_URL to a postgres database. Please use a different database than the main Cal instance since the migrations are separate for this database. For example `postgresql://postgres:@localhost:5450/cal-saml`
2. Set SAML_ADMINS to a comma separated list of admin emails from where the SAML metadata can be uploaded and configured.
3. Create a SAML application with your Identity Provider (IdP) using the instructions here - [SAML Setup](../docs/saml-setup.md)
4. Remember to configure access to the IdP SAML app for all your users (who need access to Cal).
5. You will need the XML metadata from your IdP later, so keep it accessible.
6. Log in to one of the admin accounts configured in SAML_ADMINS and then navigate to Settings -> Security.
7. You should see a SAML configuration section, copy and paste the XML metadata from step 5 and click on Save.
8. Your provisioned users can now log into Cal using SAML.

View file

@ -1,6 +1,11 @@
import slugify from "@lib/slugify";
import slugify from "@calcom/lib/slugify";
export async function checkPremiumUsername(_username: string) {
export async function checkPremiumUsername(_username: string): Promise<{
available: boolean;
premium: boolean;
message?: string;
suggestion?: string;
}> {
const username = slugify(_username);
const response = await fetch("https://cal.com/api/username", {
credentials: "include",
@ -12,14 +17,6 @@ export async function checkPremiumUsername(_username: string) {
mode: "cors",
});
if (response.ok) {
return {
available: true as const,
};
}
const json = await response.json();
return {
available: false as const,
message: json.message as string,
};
return json;
}

16
packages/ee/package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "@calcom/ee",
"description": "Cal.com Enterprise Edition features",
"version": "0.0.0",
"private": true,
"license": "AGPLv3",
"scripts": {
"clean": "rm -rf .turbo && rm -rf node_modules"
},
"dependencies": {
"@calcom/lib": "*"
},
"devDependencies": {
"@calcom/tsconfig": "*"
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "@calcom/tsconfig/base.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

View file

@ -0,0 +1,6 @@
export const BASE_URL = process.env.BASE_URL || `https://${process.env.VERCEL_URL}`;
export const WEBSITE_URL = process.env.NEXT_PUBLIC_APP_URL || "https://cal.com";
export const IS_PRODUCTION = process.env.NODE_ENV === "production";
export const TRIAL_LIMIT_DAYS = 14;
export const HOSTED_CAL_FEATURES = process.env.HOSTED_CAL_FEATURES || BASE_URL === "https://app.cal.com";
export const NEXT_PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || `https://${process.env.VERCEL_URL}`;

View file

@ -0,0 +1,33 @@
export class HttpError<TCode extends number = number> extends Error {
public readonly cause?: Error;
public readonly statusCode: TCode;
public readonly message: string;
public readonly url: string | undefined;
public readonly method: string | undefined;
constructor(opts: { url?: string; method?: string; message?: string; statusCode: TCode; cause?: Error }) {
super(opts.message ?? `HTTP Error ${opts.statusCode} `);
Object.setPrototypeOf(this, HttpError.prototype);
this.name = HttpError.prototype.constructor.name;
this.cause = opts.cause;
this.statusCode = opts.statusCode;
this.url = opts.url;
this.method = opts.method;
this.message = opts.message ?? `HTTP Error ${opts.statusCode}`;
if (opts.cause instanceof Error && opts.cause.stack) {
this.stack = opts.cause.stack;
}
}
public static fromRequest(request: Request, response: Response) {
return new HttpError({
message: response.statusText,
url: response.url,
method: request.method,
statusCode: response.status,
});
}
}

View file

@ -6,7 +6,7 @@
"license": "MIT",
"dependencies": {
"bcryptjs": "^2.4.3",
"dayjs": "^1.10.6",
"dayjs": "^1.10.4",
"dayjs-business-time": "^1.0.4"
},
"devDependencies": {

View file

@ -7,7 +7,7 @@ declare global {
export const prisma =
globalThis.prisma ||
new PrismaClient({
log: ["query", "error", "warn"],
// log: ["query", "error", "warn"],
});
if (process.env.NODE_ENV !== "production") {

42
packages/stripe/LICENSE Normal file
View file

@ -0,0 +1,42 @@
The Cal.com Enterprise Edition (EE) license (the “EE License”)
Copyright (c) 2020-present Cal.com, Inc
With regard to the Cal.com Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Cal.com Subscription Terms available
at https://cal.com/terms (the “EE Terms”), or other agreements governing
the use of the Software, as mutually agreed by you and Cal.com, Inc ("Cal.com"),
and otherwise have a valid Cal.com Enterprise Edition subscription ("EE Subscription")
for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence,
you are free to modify this Software and publish patches to the Software. You agree
that Cal.com and/or its licensors (as applicable) retain all right, title and interest in
and to all such modifications and/or patches, and all such modifications and/or
patches may only be used, copied, modified, displayed, distributed, or otherwise
exploited with a valid EE Subscription for the correct number of hosts.
Notwithstanding the foregoing, you may copy and modify the Software for development
and testing purposes, without requiring a subscription. You agree that Cal.com and/or
its licensors (as applicable) retain all right, title and interest in and to all such
modifications. You are not granted any other rights beyond what is expressly stated herein.
Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense,
and/or sell the Software.
This EE License applies only to the part of this Software that is not distributed under
the AGPLv3 license. Any part of this Software distributed under the MIT license or which
is served client-side as an image, font, cascading stylesheet (CSS), file which produces
or is compiled, arranged, augmented, or combined into client-side JavaScript, in whole or
in part, is copyrighted under the AGPLv3 license. The full text of this EE License shall
be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
For all third party components incorporated into the Cal.com Software, those
components are licensed under the original license provided by the owner of the
applicable component.

38
packages/stripe/README.md Normal file
View file

@ -0,0 +1,38 @@
<!-- PROJECT LOGO -->
<div align="center">
<a href="https://cal.com/enterprise">
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
</a>
<a href="https://cal.com/enterprise">Get Started with Enterprise</a>
</div>
# Enterprise Edition
Welcome to the Enterprise Edition ("/ee") of Cal.com.
The [/ee](https://github.com/calcom/cal.com/tree/main/apps/web/ee) subfolder is the place for all the **Pro** features from our [hosted](https://cal.com/pricing) plan and [enterprise-grade](https://cal.com/enterprise) features such as SSO, SAML, ADFS, OIDC, SCIM, SIEM, HRIS and much more.
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calcom/cal.com)). You are not allowed to use this code to host your own version of app.cal.com without obtaining a proper [license](https://cal.com/enterprise) first❗_
## Setting up Stripe
1. Create a stripe account or use an existing one. For testing, you should use all stripe dashboard functions with the Test-Mode toggle in the top right activated.
2. Open [Stripe ApiKeys](https://dashboard.stripe.com/apikeys) save the token starting with `pk_...` to `NEXT_PUBLIC_STRIPE_PUBLIC_KEY` and `sk_...` to `STRIPE_PRIVATE_KEY` in the .env file.
3. Open [Stripe Connect Settings](https://dashboard.stripe.com/settings/connect) and activate OAuth for Standard Accounts
4. Add `<CALENDSO URL>/api/integrations/stripepayment/callback` as redirect URL.
5. Copy your client*id (`ca*...`) to `STRIPE_CLIENT_ID` in the .env file.
6. Open [Stripe Webhooks](https://dashboard.stripe.com/webhooks) and add `<CALENDSO URL>/api/integrations/stripepayment/webhook` as webhook for connected applications.
7. Select all `payment_intent` events for the webhook.
8. Copy the webhook secret (`whsec_...`) to `STRIPE_WEBHOOK_SECRET` in the .env file.
## Setting up SAML login
1. Set SAML_DATABASE_URL to a postgres database. Please use a different database than the main Cal instance since the migrations are separate for this database. For example `postgresql://postgres:@localhost:5450/cal-saml`
2. Set SAML_ADMINS to a comma separated list of admin emails from where the SAML metadata can be uploaded and configured.
3. Create a SAML application with your Identity Provider (IdP) using the instructions here - [SAML Setup](../docs/saml-setup.md)
4. Remember to configure access to the IdP SAML app for all your users (who need access to Cal).
5. You will need the XML metadata from your IdP later, so keep it accessible.
6. Log in to one of the admin accounts configured in SAML_ADMINS and then navigate to Settings -> Security.
7. You should see a SAML configuration section, copy and paste the XML metadata from step 5 and click on Save.
8. Your provisioned users can now log into Cal using SAML.

View file

@ -2,7 +2,7 @@ import { Stripe } from "@stripe/stripe-js";
import { loadStripe } from "@stripe/stripe-js/pure";
import { stringify } from "querystring";
import { Maybe } from "@trpc/server";
export type Maybe<T> = T | undefined | null;
const stripePublicKey = process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY!;
let stripePromise: Promise<Stripe | null>;

View file

@ -0,0 +1,4 @@
export const FREE_PLAN_PRICE = process.env.NEXT_PUBLIC_STRIPE_FREE_PLAN_PRICE || "";
export const PREMIUM_PLAN_PRICE = process.env.NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE || "";
export const PRO_PLAN_PRICE = process.env.NEXT_PUBLIC_STRIPE_PRO_PLAN_PRICE || "";
export const PRO_PLAN_PRODUCT = process.env.NEXT_PUBLIC_STRIPE_PRO_PLAN_PRODUCT || "";

View file

@ -1,10 +1,8 @@
import { Prisma } from "@prisma/client";
import { HttpError as HttpCode } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { HttpError as HttpCode } from "@lib/core/http/error";
import stripe from "./server";
import stripe from "@calcom/stripe/server";
export async function getStripeCustomerIdFromUserId(userId: number) {
// Get user

95
packages/stripe/downgrade.ts Executable file
View file

@ -0,0 +1,95 @@
#!/usr/bin/env ts-node
// To run this script: `yarn downgrade 2>&1 | tee result.log`
import { Prisma, UserPlan } from "@prisma/client";
import dayjs from "dayjs";
import { TRIAL_LIMIT_DAYS } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
import stripe from "@calcom/stripe/server";
// import { isPremiumUserName } from "../../apps/website/lib/username";
import { getStripeCustomerIdFromUserId } from "./customer";
import { getPremiumPlanPrice, getProPlanPrice, getProPlanProduct } from "./utils";
export async function downgradeIllegalProUsers() {
const illegalProUsers = await prisma.user.findMany({
where: {
plan: UserPlan.PRO,
},
select: {
id: true,
name: true,
email: true,
username: true,
plan: true,
metadata: true,
},
});
const usersDowngraded: Partial<typeof illegalProUsers[number]>[] = [];
const downgrade = async (user: typeof illegalProUsers[number]) => {
await prisma.user.update({
where: { id: user.id },
data: {
plan: UserPlan.TRIAL,
trialEndsAt: dayjs().add(TRIAL_LIMIT_DAYS, "day").toDate(),
},
});
console.log(`Downgraded: ${user.email}`);
usersDowngraded.push({
id: user.id,
username: user.username,
name: user.name,
email: user.email,
plan: user.plan,
metadata: user.metadata,
});
};
for (const suspectUser of illegalProUsers) {
console.log(`Checking: ${suspectUser.email}`);
const metadata = (suspectUser.metadata as Prisma.JsonObject) ?? {};
// if their pro is already sponsored by a team, do not downgrade
if (metadata.proPaidForByTeamId !== undefined) continue;
const stripeCustomerId = await getStripeCustomerIdFromUserId(suspectUser.id);
const customer = await stripe.customers.retrieve(stripeCustomerId, {
expand: ["subscriptions.data.plan"],
});
if (!customer || customer.deleted) {
await downgrade(suspectUser);
continue;
}
const subscription = customer.subscriptions?.data[0];
if (!subscription) {
await downgrade(suspectUser);
continue;
}
const hasProPlan = !!subscription.items.data.find(
(item) =>
item.plan.product === getProPlanProduct() ||
[getProPlanPrice(), getPremiumPlanPrice()].includes(item.plan.id)
);
// if they're pro, do not downgrade
if (hasProPlan) continue;
// If they already have a premium username, do not downgrade
// if (suspectUser.username && isPremiumUserName(suspectUser.username)) continue;
await downgrade(suspectUser);
}
return {
usersDowngraded,
usersDowngradedAmount: usersDowngraded.length,
};
}
downgradeIllegalProUsers()
.then(({ usersDowngraded, usersDowngradedAmount }) => {
console.log(`Downgraded ${usersDowngradedAmount} illegal pro users`);
console.table(usersDowngraded);
})
.catch((e) => {
console.error(e);
process.exit(1);
});

1
packages/stripe/index.ts Normal file
View file

@ -0,0 +1 @@
export * from "./constants";

View file

@ -0,0 +1,29 @@
{
"name": "@calcom/stripe",
"version": "0.0.0",
"private": true,
"license": "AGPLv3",
"scripts": {
"clean": "rm -rf .turbo && rm -rf node_modules",
"downgrade": "ts-node ./downgrade.ts"
},
"devDependencies": {
"ts-node": "^10.6.0"
},
"dependencies": {
"@calcom/ee": "*",
"@calcom/lib": "*",
"@stripe/react-stripe-js": "^1.4.1",
"@stripe/stripe-js": "^1.16.0",
"dayjs": "^1.10.4",
"stripe": "^8.191.0"
},
"main": "index.ts",
"types": "index.d.ts",
"files": [
"client.ts",
"constants.ts",
"server.ts",
"utils.ts"
]
}

18
packages/stripe/server.ts Normal file
View file

@ -0,0 +1,18 @@
import Stripe from "stripe";
export type PaymentData = Stripe.Response<Stripe.PaymentIntent> & {
stripe_publishable_key: string;
stripeAccount: string;
};
export type StripeData = Stripe.OAuthToken & {
default_currency: string;
};
const stripePrivateKey = process.env.STRIPE_PRIVATE_KEY!;
const stripe = new Stripe(stripePrivateKey, {
apiVersion: "2020-08-27",
});
export default stripe;

View file

@ -1,13 +1,13 @@
import { MembershipRole, Prisma, UserPlan } from "@prisma/client";
import Stripe from "stripe";
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { getStripeCustomerIdFromUserId } from "@ee/lib/stripe/customer";
import stripe from "@calcom/stripe/server";
import { HOSTED_CAL_FEATURES } from "@lib/config/constants";
import { HttpError } from "@lib/core/http/error";
import stripe from "./server";
import { getStripeCustomerIdFromUserId } from "./customer";
import { getPerSeatProPlanPrice, getPremiumPlanPrice, getProPlanPrice } from "./utils";
// get team owner's Pro Plan subscription from Cal userId
export async function getProPlanSubscription(userId: number) {
@ -262,21 +262,3 @@ export async function ensureSubscriptionQuantityCorrectness(userId: number, team
await updatePerSeatQuantity(subscription, membersMissingSeats.length);
}
}
const isProductionSite =
process.env.NEXT_PUBLIC_BASE_URL === "https://app.cal.com" && process.env.VERCEL_ENV === "production";
// TODO: these should be moved to env vars
export function getPerSeatProPlanPrice(): string {
return isProductionSite ? "price_1KHkoeH8UDiwIftkkUbiggsM" : "price_1KLD4GH8UDiwIftkWQfsh1Vh";
}
export function getProPlanPrice(): string {
return isProductionSite ? "price_1KHkoeH8UDiwIftkkUbiggsM" : "price_1JZ0J3H8UDiwIftk0YIHYKr8";
}
export function getPremiumPlanPrice(): string {
return isProductionSite ? "price_1Jv3CMH8UDiwIftkFgyXbcHN" : "price_1Jv3CMH8UDiwIftkFgyXbcHN";
}
export function getProPlanProduct(): string {
return isProductionSite ? "prod_JVxwoOF5odFiZ8" : "prod_KDRBg0E4HyVZee";
}

View file

@ -0,0 +1,8 @@
{
"extends": "@calcom/tsconfig/base.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["."],
"exclude": ["dist", "build", "node_modules"]
}

21
packages/stripe/utils.ts Normal file
View file

@ -0,0 +1,21 @@
import { FREE_PLAN_PRICE, PREMIUM_PLAN_PRICE, PRO_PLAN_PRICE, PRO_PLAN_PRODUCT } from "./constants";
export function getPerSeatProPlanPrice(): string {
return PRO_PLAN_PRICE;
}
export function getPremiumPlanPrice(): string {
return PREMIUM_PLAN_PRICE;
}
export function getProPlanPrice(): string {
return PRO_PLAN_PRICE;
}
export function getProPlanProduct(): string {
return PRO_PLAN_PRODUCT;
}
export function getFreePlanPrice(): string {
return FREE_PLAN_PRICE;
}

246
yarn.lock
View file

@ -2606,13 +2606,6 @@
dependencies:
defer-to-connect "^1.0.1"
"@tailwindcss/forms@^0.3.3":
version "0.3.4"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.3.4.tgz#e4939dc16450eccf4fd2029770096f38cbb556d4"
integrity sha512-vlAoBifNJUkagB+PAdW4aHMe4pKmSLroH398UPgIogBFc91D2VlHUxe4pjxQhiJl0Nfw53sHSJSQBSTQBZP3vA==
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/forms@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.0.tgz#d4bea2560a10aac642573e72d3b4d62a88960449"
@ -2620,16 +2613,6 @@
dependencies:
mini-svg-data-uri "^1.2.3"
"@tailwindcss/typography@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.4.1.tgz#51ddbceea6a0ee9902c649dbe58871c81a831212"
integrity sha512-ovPPLUhs7zAIJfr0y1dbGlyCuPhpuv/jpBoFgqAc658DWGGrOBWBMpAWLw2KlzbNeVk4YBJMzue1ekvIbdw6XA==
dependencies:
lodash.castarray "^4.4.0"
lodash.isplainobject "^4.0.6"
lodash.merge "^4.6.2"
lodash.uniq "^4.5.0"
"@tailwindcss/typography@^0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee"
@ -4341,7 +4324,7 @@ bytes@3.1.1:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a"
integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==
bytes@3.1.2, bytes@^3.0.0:
bytes@3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
@ -4726,27 +4709,11 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.0.tgz#63b6ebd1bec11999d1df3a79a7569451ac2be8aa"
integrity sha512-9Mrz2AQLefkH1UvASKj6v6hj/7eWgjnT/cVsR8CumieLoT+g900exWeNogqtweI8dxloXN9BDQTYro1oWu/5CQ==
dependencies:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color@^4.0.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-4.2.1.tgz#498aee5fce7fc982606c8875cab080ac0547c884"
integrity sha512-MFJr0uY4RvTQUKvPq7dh9grVOTYSFeXja2mBXioCGjnjJoXrAp9jJ1NQTDR73c9nwBSAQiNKloKl5zq9WB9UPw==
dependencies:
color-convert "^2.0.1"
color-string "^1.9.0"
colorette@^2.0.16:
version "2.0.16"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da"
@ -4784,7 +4751,7 @@ commander@^6.2.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
commander@^8.0.0, commander@^8.2.0, commander@^8.3.0:
commander@^8.2.0, commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
@ -5020,16 +4987,6 @@ crypto-random-string@^2.0.0:
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
css-unit-converter@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
integrity sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@ -5093,16 +5050,11 @@ dayjs-business-time@^1.0.4:
dependencies:
dayjs "^1.10.4"
dayjs@^1.10.4, dayjs@^1.10.6:
dayjs@^1.10.4:
version "1.10.7"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
dayjs@^1.10.7:
version "1.10.8"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41"
integrity sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -6736,15 +6688,6 @@ fs-constants@^1.0.0:
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
fs-extra@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8"
integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==
dependencies:
graceful-fs "^4.2.0"
jsonfile "^6.0.1"
universalify "^2.0.0"
fs-extra@^4.0.2:
version "4.0.3"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
@ -7339,11 +7282,6 @@ hast-util-whitespace@^2.0.0:
resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c"
integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg==
hex-color-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
hexoid@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18"
@ -7387,16 +7325,6 @@ hosted-git-info@^2.1.4:
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hsl-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=
hsla-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@ -7416,11 +7344,6 @@ html-parse-stringify@^3.0.1:
dependencies:
void-elements "3.1.0"
html-tags@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.1.0.tgz#7b5e6f7e665e9fb41f30007ed9e0d41e97fb2140"
integrity sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==
html-void-elements@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
@ -7780,11 +7703,6 @@ is-arrayish@^0.2.1:
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
is-arrayish@^0.3.1:
version "0.3.2"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03"
integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==
is-bigint@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
@ -7829,18 +7747,6 @@ is-ci@^2.0.0:
dependencies:
ci-info "^2.0.0"
is-color-stop@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345"
integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=
dependencies:
css-color-names "^0.0.4"
hex-color-regex "^1.1.0"
hsl-regex "^1.0.0"
hsla-regex "^1.0.0"
rgb-regex "^1.0.1"
rgba-regex "^1.0.0"
is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
@ -8937,15 +8843,6 @@ jsonfile@^5.0.0:
optionalDependencies:
graceful-fs "^4.1.6"
jsonfile@^6.0.1:
version "6.1.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
dependencies:
universalify "^2.0.0"
optionalDependencies:
graceful-fs "^4.1.6"
jsprim@^1.2.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
@ -9302,21 +9199,11 @@ lodash.once@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash.topath@^4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.topath/-/lodash.topath-4.5.2.tgz#3616351f3bba61994a0931989660bd03254fd009"
integrity sha1-NhY1Hzu6YZlKCTGYlmC9AyVP0Ak=
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash.uniq@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.21, lodash@4.x, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@ -10254,11 +10141,6 @@ mockdate@^3.0.5:
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==
modern-normalize@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/modern-normalize/-/modern-normalize-1.1.0.tgz#da8e80140d9221426bd4f725c6e11283d34f90b7"
integrity sha512-2lMlY1Yc1+CUy0gw4H95uNN7vjbpoED7NNRSBHE25nWfLBdmMzFCsPshlzbxHz+gYMcBEUN8V4pU16prcdPSgA==
module-alias@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0"
@ -10580,13 +10462,6 @@ node-addon-api@^2.0.0:
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz#432cfa82962ce494b132e9d72a15b29f71ff5d32"
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
node-emoji@^1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/node-emoji/-/node-emoji-1.11.0.tgz#69a0150e6946e2f115e9d7ea4df7971e2628301c"
integrity sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==
dependencies:
lodash "^4.17.21"
node-fetch@2.6.7, node-fetch@^2.6.1:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
@ -11475,14 +11350,6 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-js@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-3.0.3.tgz#2f0bd370a2e8599d45439f6970403b5873abda33"
integrity sha512-gWnoWQXKFw65Hk/mi2+WTQTHdPD5UJdDXZmX073EY/B3BWnYjO4F4t0VneTCnCGQ5E5GsCdMkzPaTXwl3r5dJw==
dependencies:
camelcase-css "^2.0.1"
postcss "^8.1.6"
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
@ -11513,12 +11380,7 @@ postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^3.3.0:
version "3.3.1"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0:
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
@ -11532,7 +11394,7 @@ postcss@8.4.5:
picocolors "^1.0.0"
source-map-js "^1.0.1"
postcss@^8.1.6, postcss@^8.3.5, postcss@^8.3.6, postcss@^8.4.6:
postcss@^8.3.6, postcss@^8.4.6:
version "8.4.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.8.tgz#dad963a76e82c081a0657d3a2f3602ce10c2e032"
integrity sha512-2tXEqGxrjvAO6U+CJzDL2Fk2kPHTv1jQsYkSoMeOis2SsYaXRO2COxTdQp99cYvif9JTXaAk9lYGc3VhJt7JPQ==
@ -11645,11 +11507,6 @@ pretty-format@^3.8.0:
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
integrity sha1-v77VbV6ad2ZF9LH/eqGjrE+jw4U=
pretty-hrtime@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
integrity sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=
printj@~1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/printj/-/printj-1.3.1.tgz#9af6b1d55647a1587ac44f4c1654a4b95b8e12cb"
@ -11798,16 +11655,6 @@ pupa@^2.1.1:
dependencies:
escape-goat "^2.0.0"
purgecss@^4.0.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/purgecss/-/purgecss-4.1.3.tgz#683f6a133c8c4de7aa82fe2746d1393b214918f7"
integrity sha512-99cKy4s+VZoXnPxaoM23e5ABcP851nC2y2GROkkjS8eJaJtlciGavd7iYAw2V84WeBqggZ12l8ef44G99HmTaw==
dependencies:
commander "^8.0.0"
glob "^7.1.7"
postcss "^8.3.5"
postcss-selector-parser "^6.0.6"
pvtsutils@^1.2.0, pvtsutils@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.2.1.tgz#8212e846ca9afb21e40cebb0691755649f9f498a"
@ -12325,14 +12172,6 @@ redis@4.0.1:
"@node-redis/search" "^1.0.1"
"@node-redis/time-series" "^1.0.0"
reduce-css-calc@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz#7ef8761a28d614980dc0c982f772c93f7a99de03"
integrity sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==
dependencies:
css-unit-converter "^1.1.1"
postcss-value-parser "^3.3.0"
reflect-metadata@0.1.13, reflect-metadata@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08"
@ -12611,16 +12450,6 @@ rfdc@^1.3.0:
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"
integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==
rgb-regex@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE=
rgba-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
@ -12966,13 +12795,6 @@ simple-get@^2.7.0:
once "^1.3.1"
simple-concat "^1.0.0"
simple-swizzle@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a"
integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=
dependencies:
is-arrayish "^0.3.1"
sirv@^1.0.7:
version "1.0.19"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49"
@ -13574,44 +13396,6 @@ table@^6.0.9:
string-width "^4.2.3"
strip-ansi "^6.0.1"
tailwindcss@^2.2.15:
version "2.2.19"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-2.2.19.tgz#540e464832cd462bb9649c1484b0a38315c2653c"
integrity sha512-6Ui7JSVtXadtTUo2NtkBBacobzWiQYVjYW0ZnKaP9S1ZCKQ0w7KVNz+YSDI/j7O7KCMHbOkz94ZMQhbT9pOqjw==
dependencies:
arg "^5.0.1"
bytes "^3.0.0"
chalk "^4.1.2"
chokidar "^3.5.2"
color "^4.0.1"
cosmiconfig "^7.0.1"
detective "^5.2.0"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.7"
fs-extra "^10.0.0"
glob-parent "^6.0.1"
html-tags "^3.1.0"
is-color-stop "^1.1.0"
is-glob "^4.0.1"
lodash "^4.17.21"
lodash.topath "^4.5.2"
modern-normalize "^1.1.0"
node-emoji "^1.11.0"
normalize-path "^3.0.0"
object-hash "^2.2.0"
postcss-js "^3.0.3"
postcss-load-config "^3.1.0"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.6"
postcss-value-parser "^4.1.0"
pretty-hrtime "^1.0.3"
purgecss "^4.0.3"
quick-lru "^5.1.1"
reduce-css-calc "^2.1.8"
resolve "^1.20.0"
tmp "^0.2.1"
tailwindcss@^3.0.23:
version "3.0.23"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10"
@ -13771,13 +13555,6 @@ titleize@1.0.0:
resolved "https://registry.yarnpkg.com/titleize/-/titleize-1.0.0.tgz#7d350722061830ba6617631e0cfd3ea08398d95a"
integrity sha1-fTUHIgYYMLpmF2MeDP0+oIOY2Vo=
tmp@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==
dependencies:
rimraf "^3.0.0"
tmpl@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
@ -13930,9 +13707,9 @@ ts-morph@^13.0.2:
code-block-writer "^11.0.0"
ts-node@^10.6.0:
version "10.6.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.6.0.tgz#c3f4195d5173ce3affdc8f2fd2e9a7ac8de5376a"
integrity sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==
version "10.7.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5"
integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@tsconfig/node10" "^1.0.7"
@ -14396,11 +14173,6 @@ universalify@^0.1.0, universalify@^0.1.2:
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
universalify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
unload@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"