Files
webstudio/apps/builder/app/shared/db/user-plan-features.server.ts
T
Ivan Starkov 3105b5cec0 feat: Make it easy for a self-hosted version to set the plan features (#3475)
## Description

closes #3400 


`+` on dev login its now possible to add any email
like 
`{code}:hello@world.xx` to login as other user


## Steps for reproduction

Login on main with `{code}:ice@yandex.ru`
- See no plan
<img width="213" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/65f79295-63b3-4d4f-b5d2-ed854f8462e3">

---

Login on main as usual
- See user has plan
<img width="265" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/db9e1c3e-a8d7-4cde-bbd5-1f39ce7442b8">

---

Login locally
See user has plan

<img width="231" alt="image"
src="https://github.com/webstudio-is/webstudio/assets/5077042/c1187bce-2c7e-49f3-a3ac-bfa15560ccce">




## Code Review

- [ ] hi @kof, I need you to do
  - conceptual review (architecture, feature-correctness)
  - detailed review (read every line)
  - test it on preview

## Before requesting a review

- [ ] made a self-review
- [ ] added inline comments where things may be not obvious (the "why",
not "what")

## Before merging

- [ ] tested locally and on preview environment (preview dev login:
5de6)
- [ ] updated [test
cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md)
document
- [ ] added tests
- [ ] if any new env variables are added, added them to `.env.example`
and the `builder/env-check.js` if mandatory
2024-06-02 14:54:19 +03:00

96 lines
2.3 KiB
TypeScript

import { prisma } from "@webstudio-is/prisma-client";
import type { AppContext } from "@webstudio-is/trpc-interface/index.server";
import env from "~/env/env.server";
export type UserPlanFeatures = NonNullable<AppContext["userPlanFeatures"]>;
export const getTokenPlanFeatures = async (token: string) => {
const projectOwnerIdByToken = await prisma.authorizationToken.findUnique({
where: {
token,
},
select: {
project: {
select: {
id: true,
userId: true,
},
},
},
});
if (projectOwnerIdByToken === null) {
throw new Error(`Project owner can't be found for token ${token}`);
}
const userId = projectOwnerIdByToken.project.userId;
if (userId === null) {
throw new Error(
`Project ${projectOwnerIdByToken.project.id} has null instead of userId`
);
}
return await getUserPlanFeatures(userId);
};
export const getUserPlanFeatures = async (
userId: string
): Promise<UserPlanFeatures> => {
const userProducts = await prisma.userProduct.findMany({
where: { userId },
select: {
customerId: true,
subscriptionId: true,
product: {
select: {
id: true,
name: true,
description: true,
features: true,
meta: true,
images: true,
},
},
},
});
// This is fast and dirty implementation
// @todo: implement this using products meta, custom table with aggregated transaction info
if (userProducts.length > 0) {
const hasSubscription = userProducts.some(
(log) => log.subscriptionId !== null
);
return {
allowShareAdminLinks: true,
allowDynamicData: true,
allowContactEmail: true,
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
hasSubscription,
hasProPlan: true,
planName: userProducts[0].product.name,
};
}
if (env.USER_PLAN === "pro") {
return {
allowShareAdminLinks: true,
allowDynamicData: true,
allowContactEmail: true,
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
hasSubscription: true,
hasProPlan: true,
planName: "env.USER_PLAN Pro",
};
}
return {
allowShareAdminLinks: false,
allowDynamicData: false,
allowContactEmail: false,
maxDomainsAllowedPerUser: 5,
hasSubscription: false,
hasProPlan: false,
};
};