This commit is contained in:
Owen
2025-10-04 18:36:44 -07:00
parent 3123f858bb
commit c2c907852d
320 changed files with 35785 additions and 2984 deletions

View File

@@ -10,6 +10,8 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { checkValidInvite } from "@server/auth/checkValidInvite";
import { verifySession } from "@server/auth/sessions/verifySession";
import { usageService } from "@server/lib/private/billing/usageService";
import { FeatureId } from "@server/lib/private/billing";
const acceptInviteBodySchema = z
.object({
@@ -131,6 +133,14 @@ export async function acceptInvite(
.where(eq(userOrgs.orgId, existingInvite.orgId));
});
if (totalUsers) {
await usageService.updateDaily(
existingInvite.orgId,
FeatureId.USERS,
totalUsers.length
);
}
return response<AcceptInviteResponse>(res, {
data: { accepted: true, orgId: existingInvite.orgId },
success: true,

View File

@@ -10,6 +10,11 @@ import { db, UserOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db";
import { generateId } from "@server/auth/sessions/app";
import { usageService } from "@server/lib/private/billing/usageService";
import { FeatureId } from "@server/lib/private/billing";
import { build } from "@server/build";
import { getOrgTierData } from "@server/routers/private/billing";
import { TierId } from "@server/lib/private/billing/tiers";
const paramsSchema = z
.object({
@@ -93,6 +98,35 @@ export async function createOrgUser(
roleId
} = parsedBody.data;
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
if (!usage) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"No usage data found for this organization"
)
);
}
const rejectUsers = await usageService.checkLimitSet(
orgId,
false,
FeatureId.USERS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
} // We need to add one to know if we are violating the limit
);
if (rejectUsers) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User limit exceeded. Please upgrade your plan."
)
);
}
}
const [role] = await db
.select()
.from(roles)
@@ -112,6 +146,19 @@ export async function createOrgUser(
)
);
} else if (type === "oidc") {
if (build === "saas") {
const { tier } = await getOrgTierData(orgId);
const subscribed = tier === TierId.STANDARD;
if (!subscribed) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"This organization's current plan does not support this feature."
)
);
}
}
if (!idpId) {
return next(
createHttpError(
@@ -218,6 +265,14 @@ export async function createOrgUser(
.from(userOrgs)
.where(eq(userOrgs.orgId, orgId));
});
if (orgUsers) {
await usageService.updateDaily(
orgId,
FeatureId.USERS,
orgUsers.length
);
}
} else {
return next(
createHttpError(HttpCode.BAD_REQUEST, "User type is required")

View File

@@ -17,6 +17,9 @@ import { sendEmail } from "@server/emails";
import SendInviteLink from "@server/emails/templates/SendInviteLink";
import { OpenAPITags, registry } from "@server/openApi";
import { UserType } from "@server/types/UserTypes";
import { usageService } from "@server/lib/private/billing/usageService";
import { FeatureId } from "@server/lib/private/billing";
import { build } from "@server/build";
const regenerateTracker = new NodeCache({ stdTTL: 3600, checkperiod: 600 });
@@ -28,10 +31,7 @@ const inviteUserParamsSchema = z
const inviteUserBodySchema = z
.object({
email: z
.string()
.toLowerCase()
.email(),
email: z.string().toLowerCase().email(),
roleId: z.number(),
validHours: z.number().gt(0).lte(168),
sendEmail: z.boolean().optional(),
@@ -99,7 +99,6 @@ export async function inviteUser(
regenerate
} = parsedBody.data;
// Check if the organization exists
const org = await db
.select()
@@ -112,6 +111,35 @@ export async function inviteUser(
);
}
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
if (!usage) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
"No usage data found for this organization"
)
);
}
const rejectUsers = await usageService.checkLimitSet(
orgId,
false,
FeatureId.USERS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
} // We need to add one to know if we are violating the limit
);
if (rejectUsers) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User limit exceeded. Please upgrade your plan."
)
);
}
}
// Check if the user already exists in the `users` table
const existingUser = await db
.select()

View File

@@ -2,13 +2,17 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, resources, sites, UserOrg } from "@server/db";
import { userOrgs, userResources, users, userSites } from "@server/db";
import { and, eq, exists } from "drizzle-orm";
import { and, count, eq, exists } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { usageService } from "@server/lib/private/billing/usageService";
import { FeatureId } from "@server/lib/private/billing";
import { build } from "@server/build";
import { UserType } from "@server/types/UserTypes";
const removeUserSchema = z
.object({
@@ -115,8 +119,33 @@ export async function removeUserOrg(
.select()
.from(userOrgs)
.where(eq(userOrgs.orgId, orgId));
if (build === "saas") {
const [rootUser] = await trx
.select()
.from(users)
.where(eq(users.userId, userId));
const [leftInOrgs] = await trx
.select({ count: count() })
.from(userOrgs)
.where(eq(userOrgs.userId, userId));
// if the user is not an internal user and does not belong to any org, delete the entire user
if (rootUser?.type !== UserType.Internal && !leftInOrgs.count) {
await trx.delete(users).where(eq(users.userId, userId));
}
}
});
if (userCount) {
await usageService.updateDaily(
orgId,
FeatureId.USERS,
userCount.length
);
}
return response(res, {
data: null,
success: true,