mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-02 18:18:35 +09:00
- Replace workspaces feature flag with maxWorkspaces-based gating - Default maxWorkspaces to 0 (controlled via MAX_WORKSPACES env var) - Skip workspace queries in dashboard loader when maxWorkspaces is 0 - Extract pure functions (resolveCurrentWorkspace, isWorkspaceRelationPermitted, getView) with tests - Remove workspaces feature flag from all files
142 lines
4.4 KiB
TypeScript
142 lines
4.4 KiB
TypeScript
import type { AppContext } from "@webstudio-is/trpc-interface/index.server";
|
|
import type { UserPlanFeatures } from "@webstudio-is/trpc-interface/user-plan-features";
|
|
import env from "~/env/env.server";
|
|
|
|
type UserPlanInfo = {
|
|
userPlanFeatures: UserPlanFeatures;
|
|
purchases: AppContext["purchases"];
|
|
};
|
|
|
|
export const getUserPlanInfo = async (
|
|
userId: string,
|
|
postgrest: AppContext["postgrest"]
|
|
): Promise<UserPlanInfo> => {
|
|
const userProductsResult = await postgrest.client
|
|
.from("UserProduct")
|
|
.select("customerId, subscriptionId, productId")
|
|
.eq("userId", userId);
|
|
|
|
if (userProductsResult.error) {
|
|
console.error(userProductsResult.error);
|
|
throw new Error("Failed to fetch user products");
|
|
}
|
|
|
|
const userProducts = userProductsResult.data;
|
|
|
|
const productsResult = await postgrest.client
|
|
.from("Product")
|
|
.select("id, name, meta")
|
|
.in(
|
|
"id",
|
|
userProducts.map(({ productId }) => productId ?? "")
|
|
);
|
|
|
|
if (productsResult.error) {
|
|
console.error(productsResult.error);
|
|
throw new Error("Failed to fetch products");
|
|
}
|
|
|
|
const products = productsResult.data;
|
|
|
|
// Create a map of productId -> product name for quick lookup
|
|
const productIdToName = new Map<string, string>();
|
|
for (const product of products) {
|
|
productIdToName.set(product.id, product.name);
|
|
}
|
|
|
|
// Build purchases array - includes all products (subscriptions and LTDs)
|
|
// subscriptionId only set for recurring subscriptions (manageable in Stripe)
|
|
const purchases: Array<{
|
|
planName: string;
|
|
subscriptionId?: string;
|
|
}> = [];
|
|
for (const userProduct of userProducts) {
|
|
if (userProduct.productId) {
|
|
const planName = productIdToName.get(userProduct.productId) ?? "Pro";
|
|
purchases.push({
|
|
planName,
|
|
subscriptionId: userProduct.subscriptionId ?? undefined,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (userProducts.length > 0) {
|
|
const productMetas = products.map((product) => {
|
|
return {
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowAdditionalPermissions: true,
|
|
allowDynamicData: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
maxContactEmails: 5,
|
|
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
|
|
maxPublishesAllowedPerUser: Number.MAX_SAFE_INTEGER,
|
|
maxWorkspaces: env.MAX_WORKSPACES,
|
|
...(product.meta as Partial<UserPlanFeatures>),
|
|
};
|
|
});
|
|
return {
|
|
userPlanFeatures: {
|
|
canDownloadAssets: productMetas.some((item) => item.canDownloadAssets),
|
|
canRestoreBackups: productMetas.some((item) => item.canRestoreBackups),
|
|
allowAdditionalPermissions: productMetas.some(
|
|
(item) => item.allowAdditionalPermissions
|
|
),
|
|
allowDynamicData: productMetas.some((item) => item.allowDynamicData),
|
|
allowContentMode: productMetas.some((item) => item.allowContentMode),
|
|
allowStagingPublish: productMetas.some(
|
|
(item) => item.allowStagingPublish
|
|
),
|
|
maxContactEmails: Math.max(
|
|
...productMetas.map((item) => item.maxContactEmails)
|
|
),
|
|
maxDomainsAllowedPerUser: Math.max(
|
|
...productMetas.map((item) => item.maxDomainsAllowedPerUser)
|
|
),
|
|
maxPublishesAllowedPerUser: Math.max(
|
|
...productMetas.map((item) => item.maxPublishesAllowedPerUser)
|
|
),
|
|
maxWorkspaces: Math.max(
|
|
...productMetas.map((item) => item.maxWorkspaces)
|
|
),
|
|
},
|
|
purchases,
|
|
};
|
|
}
|
|
|
|
if (env.USER_PLAN === "pro") {
|
|
return {
|
|
userPlanFeatures: {
|
|
canDownloadAssets: true,
|
|
canRestoreBackups: true,
|
|
allowAdditionalPermissions: true,
|
|
allowDynamicData: true,
|
|
allowContentMode: true,
|
|
allowStagingPublish: true,
|
|
maxContactEmails: 5,
|
|
maxDomainsAllowedPerUser: Number.MAX_SAFE_INTEGER,
|
|
maxPublishesAllowedPerUser: Number.MAX_SAFE_INTEGER,
|
|
maxWorkspaces: env.MAX_WORKSPACES,
|
|
},
|
|
purchases: [{ planName: "env.USER_PLAN Pro" }],
|
|
};
|
|
}
|
|
|
|
return {
|
|
userPlanFeatures: {
|
|
canDownloadAssets: false,
|
|
canRestoreBackups: false,
|
|
allowAdditionalPermissions: false,
|
|
allowDynamicData: false,
|
|
allowContentMode: false,
|
|
allowStagingPublish: false,
|
|
maxContactEmails: 0,
|
|
maxDomainsAllowedPerUser: 0,
|
|
maxPublishesAllowedPerUser: 10,
|
|
maxWorkspaces: env.MAX_WORKSPACES,
|
|
},
|
|
purchases: [],
|
|
};
|
|
};
|