Rename to limit id

This commit is contained in:
Owen
2026-06-29 15:22:35 -04:00
parent 4718c489d3
commit ff89a64453
24 changed files with 159 additions and 154 deletions

View File

@@ -1,4 +1,4 @@
export enum FeatureId {
export enum LimitId {
USERS = "users",
SITES = "sites",
EGRESS_DATA_MB = "egressDataMb",
@@ -8,21 +8,23 @@ export enum FeatureId {
TIER1 = "tier1"
}
export async function getFeatureDisplayName(featureId: FeatureId): Promise<string> {
export async function getFeatureDisplayName(
featureId: LimitId
): Promise<string> {
switch (featureId) {
case FeatureId.USERS:
case LimitId.USERS:
return "Users";
case FeatureId.SITES:
case LimitId.SITES:
return "Sites";
case FeatureId.EGRESS_DATA_MB:
case LimitId.EGRESS_DATA_MB:
return "Egress Data (MB)";
case FeatureId.DOMAINS:
case LimitId.DOMAINS:
return "Domains";
case FeatureId.REMOTE_EXIT_NODES:
case LimitId.REMOTE_EXIT_NODES:
return "Remote Exit Nodes";
case FeatureId.ORGINIZATIONS:
case LimitId.ORGINIZATIONS:
return "Organizations";
case FeatureId.TIER1:
case LimitId.TIER1:
return "Home Lab";
default:
return featureId;
@@ -30,15 +32,16 @@ export async function getFeatureDisplayName(featureId: FeatureId): Promise<strin
}
// this is from the old system
export const FeatureMeterIds: Partial<Record<FeatureId, string>> = { // right now we are not charging for any data
export const FeatureMeterIds: Partial<Record<LimitId, string>> = {
// right now we are not charging for any data
// [FeatureId.EGRESS_DATA_MB]: "mtr_61Srreh9eWrExDSCe41D3Ee2Ir7Wm5YW"
};
export const FeatureMeterIdsSandbox: Partial<Record<FeatureId, string>> = {
export const FeatureMeterIdsSandbox: Partial<Record<LimitId, string>> = {
// [FeatureId.EGRESS_DATA_MB]: "mtr_test_61Snh2a2m6qome5Kv41DCpkOb237B3dQ"
};
export function getFeatureMeterId(featureId: FeatureId): string | undefined {
export function getFeatureMeterId(featureId: LimitId): string | undefined {
if (
process.env.ENVIRONMENT == "prod" &&
process.env.SANDBOX_MODE !== "true"
@@ -49,22 +52,20 @@ export function getFeatureMeterId(featureId: FeatureId): string | undefined {
}
}
export function getFeatureIdByMetricId(
metricId: string
): FeatureId | undefined {
return (Object.entries(FeatureMeterIds) as [FeatureId, string][]).find(
export function getFeatureIdByMetricId(metricId: string): LimitId | undefined {
return (Object.entries(FeatureMeterIds) as [LimitId, string][]).find(
([_, v]) => v === metricId
)?.[0];
}
export type FeaturePriceSet = Partial<Record<FeatureId, string>>;
export type FeaturePriceSet = Partial<Record<LimitId, string>>;
export const tier1FeaturePriceSet: FeaturePriceSet = {
[FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
[LimitId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
};
export const tier1FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
[LimitId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
};
export function getTier1FeaturePriceSet(): FeaturePriceSet {
@@ -79,11 +80,11 @@ export function getTier1FeaturePriceSet(): FeaturePriceSet {
}
export const tier2FeaturePriceSet: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
[LimitId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
};
export const tier2FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
[LimitId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
};
export function getTier2FeaturePriceSet(): FeaturePriceSet {
@@ -98,11 +99,11 @@ export function getTier2FeaturePriceSet(): FeaturePriceSet {
}
export const tier3FeaturePriceSet: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
[LimitId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
};
export const tier3FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
[LimitId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
};
export function getTier3FeaturePriceSet(): FeaturePriceSet {
@@ -116,7 +117,7 @@ export function getTier3FeaturePriceSet(): FeaturePriceSet {
}
}
export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
export function getFeatureIdByPriceId(priceId: string): LimitId | undefined {
// Check all feature price sets
const allPriceSets = [
getTier1FeaturePriceSet(),
@@ -125,7 +126,7 @@ export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
];
for (const priceSet of allPriceSets) {
const entry = (Object.entries(priceSet) as [FeatureId, string][]).find(
const entry = (Object.entries(priceSet) as [LimitId, string][]).find(
([_, price]) => price === priceId
);
if (entry) {

View File

@@ -1,19 +1,19 @@
import Stripe from "stripe";
import { FeatureId, FeaturePriceSet } from "./features";
import { LimitId, FeaturePriceSet } from "./features";
import { usageService } from "./usageService";
export async function getLineItems(
featurePriceSet: FeaturePriceSet,
orgId: string,
orgId: string
): Promise<Stripe.Checkout.SessionCreateParams.LineItem[]> {
const users = await usageService.getUsage(orgId, FeatureId.USERS);
const users = await usageService.getUsage(orgId, LimitId.USERS);
return Object.entries(featurePriceSet).map(([featureId, priceId]) => {
let quantity: number | undefined;
if (featureId === FeatureId.USERS) {
if (featureId === LimitId.USERS) {
quantity = users?.instantaneousValue || 1;
} else if (featureId === FeatureId.TIER1) {
} else if (featureId === LimitId.TIER1) {
quantity = 1;
}

View File

@@ -1,70 +1,70 @@
import { FeatureId } from "./features";
import { LimitId } from "./features";
export type LimitSet = Partial<{
[key in FeatureId]: {
[key in LimitId]: {
value: number | null; // null indicates no limit
description?: string;
};
}>;
export const freeLimitSet: LimitSet = {
[FeatureId.SITES]: { value: 5, description: "Basic limit" },
[FeatureId.USERS]: { value: 5, description: "Basic limit" },
[FeatureId.DOMAINS]: { value: 5, description: "Basic limit" },
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Basic limit" },
[LimitId.SITES]: { value: 5, description: "Basic limit" },
[LimitId.USERS]: { value: 5, description: "Basic limit" },
[LimitId.DOMAINS]: { value: 5, description: "Basic limit" },
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
[LimitId.ORGINIZATIONS]: { value: 1, description: "Basic limit" }
};
export const tier1LimitSet: LimitSet = {
[FeatureId.USERS]: { value: 7, description: "Home limit" },
[FeatureId.SITES]: { value: 10, description: "Home limit" },
[FeatureId.DOMAINS]: { value: 10, description: "Home limit" },
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Home limit" },
[LimitId.USERS]: { value: 7, description: "Home limit" },
[LimitId.SITES]: { value: 10, description: "Home limit" },
[LimitId.DOMAINS]: { value: 10, description: "Home limit" },
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
[LimitId.ORGINIZATIONS]: { value: 1, description: "Home limit" }
};
export const tier2LimitSet: LimitSet = {
[FeatureId.USERS]: {
[LimitId.USERS]: {
value: 50,
description: "Team limit"
},
[FeatureId.SITES]: {
[LimitId.SITES]: {
value: 50,
description: "Team limit"
},
[FeatureId.DOMAINS]: {
[LimitId.DOMAINS]: {
value: 50,
description: "Team limit"
},
[FeatureId.REMOTE_EXIT_NODES]: {
[LimitId.REMOTE_EXIT_NODES]: {
value: 3,
description: "Team limit"
},
[FeatureId.ORGINIZATIONS]: {
[LimitId.ORGINIZATIONS]: {
value: 1,
description: "Team limit"
}
};
export const tier3LimitSet: LimitSet = {
[FeatureId.USERS]: {
[LimitId.USERS]: {
value: 250,
description: "Business limit"
},
[FeatureId.SITES]: {
[LimitId.SITES]: {
value: 250,
description: "Business limit"
},
[FeatureId.DOMAINS]: {
[LimitId.DOMAINS]: {
value: 100,
description: "Business limit"
},
[FeatureId.REMOTE_EXIT_NODES]: {
[LimitId.REMOTE_EXIT_NODES]: {
value: 20,
description: "Business limit"
},
[FeatureId.ORGINIZATIONS]: {
[LimitId.ORGINIZATIONS]: {
value: 5,
description: "Business limit"
},
}
};

View File

@@ -1,7 +1,7 @@
import { db, limits } from "@server/db";
import { and, eq } from "drizzle-orm";
import { LimitSet } from "./limitSet";
import { FeatureId } from "./features";
import { LimitId } from "./features";
import logger from "@server/logger";
class LimitService {
@@ -38,7 +38,7 @@ class LimitService {
async getOrgLimit(
orgId: string,
featureId: FeatureId
featureId: LimitId
): Promise<number | null> {
const limitId = `${orgId}-${featureId}`;
const [limit] = await db

View File

@@ -9,7 +9,7 @@ import {
Transaction,
orgs
} from "@server/db";
import { FeatureId, getFeatureMeterId } from "./features";
import { LimitId, getFeatureMeterId } from "./features";
import logger from "@server/logger";
import { build } from "@server/build";
import { regionalCache as cache } from "#dynamic/lib/cache";
@@ -37,7 +37,7 @@ export class UsageService {
public async add(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
value: number,
transaction: any = null
): Promise<Usage | null> {
@@ -114,7 +114,7 @@ export class UsageService {
private async internalAddUsage(
orgId: string, // here the orgId is the billing org already resolved by getBillingOrg in updateCount
featureId: FeatureId,
featureId: LimitId,
value: number,
trx: Transaction
): Promise<Usage> {
@@ -163,7 +163,7 @@ export class UsageService {
async updateCount(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
value?: number,
customerId?: string
): Promise<void> {
@@ -227,7 +227,7 @@ export class UsageService {
private async getCustomerId(
orgId: string,
featureId: FeatureId
featureId: LimitId
): Promise<string | null> {
const orgIdToUse = await this.getBillingOrg(orgId);
@@ -269,7 +269,7 @@ export class UsageService {
public async getUsage(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
trx: Transaction | typeof db = db
): Promise<Usage | null> {
if (noop()) {
@@ -376,7 +376,7 @@ export class UsageService {
public async checkLimitSet(
orgId: string,
featureId?: FeatureId,
featureId?: LimitId,
usage?: Usage,
trx: Transaction | typeof db = db
): Promise<boolean> {
@@ -424,7 +424,7 @@ export class UsageService {
} else {
currentUsage = await this.getUsage(
orgIdToUse,
limit.featureId as FeatureId,
limit.featureId as LimitId,
trx
);
}

View File

@@ -24,7 +24,7 @@ import { deletePeer } from "@server/routers/gerbil/peers";
import { OlmErrorCodes } from "@server/routers/olm/error";
import { sendTerminateClient } from "@server/routers/client/terminate";
import { usageService } from "./billing/usageService";
import { FeatureId } from "./billing";
import { LimitId } from "./billing";
export type DeleteOrgByIdResult = {
deletedNewtIds: string[];
@@ -140,7 +140,9 @@ export async function deleteOrgById(
.select({ count: count() })
.from(orgDomains)
.where(eq(orgDomains.domainId, domainId));
logger.info(`Found ${orgCount.count} orgs using domain ${domainId}`);
logger.info(
`Found ${orgCount.count} orgs using domain ${domainId}`
);
if (orgCount.count === 1) {
domainIdsToDelete.push(domainId);
}
@@ -152,7 +154,7 @@ export async function deleteOrgById(
.where(inArray(domains.domainId, domainIdsToDelete));
}
await usageService.add(orgId, FeatureId.ORGINIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here
await usageService.add(orgId, LimitId.ORGINIZATIONS, -1, trx); // here we are decreasing the org count BEFORE deleting the org because we need to still be able to get the org to get the billing org inside of here
await trx.delete(orgs).where(eq(orgs.orgId, orgId));
@@ -199,22 +201,22 @@ export async function deleteOrgById(
if (org.billingOrgId) {
usageService.updateCount(
org.billingOrgId,
FeatureId.DOMAINS,
LimitId.DOMAINS,
domainCount ?? 0
);
usageService.updateCount(
org.billingOrgId,
FeatureId.SITES,
LimitId.SITES,
siteCount ?? 0
);
usageService.updateCount(
org.billingOrgId,
FeatureId.USERS,
LimitId.USERS,
userCount ?? 0
);
usageService.updateCount(
org.billingOrgId,
FeatureId.REMOTE_EXIT_NODES,
LimitId.REMOTE_EXIT_NODES,
remoteExitNodeCount ?? 0
);
}

View File

@@ -14,7 +14,7 @@ import {
} from "@server/db";
import { eq, and, inArray, ne, exists } from "drizzle-orm";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
export async function assignUserToOrg(
org: Org,
@@ -61,7 +61,7 @@ export async function assignUserToOrg(
);
if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) {
await usageService.add(org.orgId, FeatureId.USERS, 1, trx);
await usageService.add(org.orgId, LimitId.USERS, 1, trx);
}
}
}
@@ -157,7 +157,7 @@ export async function removeUserFromOrg(
);
if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) {
await usageService.add(org.orgId, FeatureId.USERS, -1, trx);
await usageService.add(org.orgId, LimitId.USERS, -1, trx);
}
}
}

View File

@@ -25,7 +25,7 @@ import {
getTier1FeaturePriceSet,
getTier3FeaturePriceSet,
getTier2FeaturePriceSet,
FeatureId,
LimitId,
type FeaturePriceSet
} from "@server/lib/billing";
import { getLineItems } from "@server/lib/billing/getLineItems";
@@ -214,7 +214,7 @@ export async function changeTier(
}
// Map to the corresponding feature in the new tier
const newPriceId = targetPriceSet[FeatureId.USERS];
const newPriceId = targetPriceSet[LimitId.USERS];
if (newPriceId) {
return {

View File

@@ -24,7 +24,7 @@ import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { Limit, limits, Usage, usage } from "@server/db";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { GetOrgUsageResponse } from "@server/routers/billing/types";
const getOrgSchema = z.strictObject({
@@ -93,16 +93,16 @@ export async function getOrgUsage(
// Get usage for org
const usageData = [];
const sites = await usageService.getUsage(orgId, FeatureId.SITES);
const users = await usageService.getUsage(orgId, FeatureId.USERS);
const domains = await usageService.getUsage(orgId, FeatureId.DOMAINS);
const sites = await usageService.getUsage(orgId, LimitId.SITES);
const users = await usageService.getUsage(orgId, LimitId.USERS);
const domains = await usageService.getUsage(orgId, LimitId.DOMAINS);
const remoteExitNodes = await usageService.getUsage(
orgId,
FeatureId.REMOTE_EXIT_NODES
LimitId.REMOTE_EXIT_NODES
);
const organizations = await usageService.getUsage(
orgId,
FeatureId.ORGINIZATIONS
LimitId.ORGINIZATIONS
);
// const egressData = await usageService.getUsage(
// orgId,

View File

@@ -35,7 +35,7 @@ import logger from "@server/logger";
import { and, eq, inArray, ne } from "drizzle-orm";
import { getNextAvailableSubnet } from "@server/lib/exitNodes";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { CreateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
export const paramsSchema = z.object({
@@ -79,7 +79,10 @@ export async function createRemoteExitNode(
const { remoteExitNodeId, secret } = parsedBody.data;
if (req.user && (!req.userOrgRoleIds || req.userOrgRoleIds.length === 0)) {
if (
req.user &&
(!req.userOrgRoleIds || req.userOrgRoleIds.length === 0)
) {
return next(
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
);
@@ -87,13 +90,13 @@ export async function createRemoteExitNode(
const usage = await usageService.getUsage(
orgId,
FeatureId.REMOTE_EXIT_NODES
LimitId.REMOTE_EXIT_NODES
);
if (usage) {
const rejectRemoteExitNodes = await usageService.checkLimitSet(
orgId,
FeatureId.REMOTE_EXIT_NODES,
LimitId.REMOTE_EXIT_NODES,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
@@ -264,7 +267,7 @@ export async function createRemoteExitNode(
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
await usageService.add(
orgId,
FeatureId.REMOTE_EXIT_NODES,
LimitId.REMOTE_EXIT_NODES,
1,
trx
);

View File

@@ -22,7 +22,7 @@ import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
const paramsSchema = z.strictObject({
orgId: z.string().min(1),
@@ -117,7 +117,7 @@ export async function deleteRemoteExitNode(
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
await usageService.add(
orgId,
FeatureId.REMOTE_EXIT_NODES,
LimitId.REMOTE_EXIT_NODES,
-1,
trx
);

View File

@@ -20,7 +20,7 @@ import { getOrgTierData } from "#dynamic/lib/billing";
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
import { UserType } from "@server/types/UserTypes";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
const deleteMyAccountBody = z.strictObject({
password: z.string().optional(),
@@ -220,7 +220,7 @@ export async function deleteMyAccount(
await trx.delete(users).where(eq(users.userId, userId));
// loop through the other orgs and decrement the count
for (const userOrg of otherOrgsTheUserWasIn) {
await usageService.add(userOrg.orgId, FeatureId.USERS, -1, trx);
await usageService.add(userOrg.orgId, LimitId.USERS, -1, trx);
}
});

View File

@@ -17,7 +17,7 @@ import { subdomainSchema } from "@server/lib/schemas";
import { generateId } from "@server/auth/sessions/app";
import { eq, and } from "drizzle-orm";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { isSecondLevelDomain, isValidDomain } from "@server/lib/validators";
import { build } from "@server/build";
import config from "@server/lib/config";
@@ -120,7 +120,7 @@ export async function createOrgDomain(
}
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.DOMAINS);
const usage = await usageService.getUsage(orgId, LimitId.DOMAINS);
if (!usage) {
return next(
createHttpError(
@@ -132,7 +132,7 @@ export async function createOrgDomain(
const rejectDomains = await usageService.checkLimitSet(
orgId,
FeatureId.DOMAINS,
LimitId.DOMAINS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
@@ -346,7 +346,7 @@ export async function createOrgDomain(
await trx.insert(dnsRecords).values(recordsToInsert);
}
await usageService.add(orgId, FeatureId.DOMAINS, 1, trx);
await usageService.add(orgId, LimitId.DOMAINS, 1, trx);
});
if (!returned) {

View File

@@ -8,7 +8,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { and, eq } from "drizzle-orm";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
const paramsSchema = z.strictObject({
domainId: z.string(),
@@ -77,7 +77,7 @@ export async function deleteAccountDomain(
await trx.delete(domains).where(eq(domains.domainId, domainId));
await usageService.add(orgId, FeatureId.DOMAINS, -1, trx);
await usageService.add(orgId, LimitId.DOMAINS, -1, trx);
});
return response<DeleteAccountDomainResponse>(res, {

View File

@@ -6,7 +6,7 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import response from "@server/lib/response";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing/features";
import { LimitId } from "@server/lib/billing/features";
import { checkExitNodeOrg } from "#dynamic/lib/exitNodes";
import { build } from "@server/build";
@@ -171,8 +171,9 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
}
// PostgreSQL: batch UPDATE … FROM (VALUES …) - single round-trip per chunk.
const valuesList = chunk.map(([publicKey, { bytesIn, bytesOut }]) =>
sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)`
const valuesList = chunk.map(
([publicKey, { bytesIn, bytesOut }]) =>
sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)`
);
const valuesClause = sql.join(valuesList, sql`, `);
return dbQueryRows<{ orgId: string; pubKey: string }>(sql`
@@ -228,7 +229,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
const totalBandwidth = orgUsageMap.get(orgId)!;
const bandwidthUsage = await usageService.add(
orgId,
FeatureId.EGRESS_DATA_MB,
LimitId.EGRESS_DATA_MB,
totalBandwidth
);
if (bandwidthUsage) {
@@ -236,7 +237,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
usageService
.checkLimitSet(
orgId,
FeatureId.EGRESS_DATA_MB,
LimitId.EGRESS_DATA_MB,
bandwidthUsage
)
.catch((error: any) => {
@@ -247,10 +248,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
});
}
} catch (error) {
logger.error(
`Error processing usage for org ${orgId}:`,
error
);
logger.error(`Error processing usage for org ${orgId}:`, error);
// Continue with other orgs.
}
}

View File

@@ -31,7 +31,7 @@ import {
} from "@server/auth/sessions/app";
import { decrypt } from "@server/lib/crypto";
import { UserType } from "@server/types/UserTypes";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { usageService } from "@server/lib/billing/usageService";
import { build } from "@server/build";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
@@ -645,7 +645,7 @@ export async function validateOidcCallback(
for (const orgCount of orgUserCounts) {
await usageService.updateCount(
orgCount.orgId,
FeatureId.USERS,
LimitId.USERS,
orgCount.userCount
);
}

View File

@@ -25,7 +25,7 @@ import { getUniqueSiteName } from "@server/db/names";
import moment from "moment";
import { build } from "@server/build";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { INSPECT_MAX_BYTES } from "buffer";
import { getNextAvailableClientSubnet } from "@server/lib/ip";
@@ -169,7 +169,7 @@ export async function registerNewt(
// SaaS billing check
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.SITES);
const usage = await usageService.getUsage(orgId, LimitId.SITES);
if (!usage) {
return next(
createHttpError(
@@ -180,7 +180,7 @@ export async function registerNewt(
}
const rejectSites = await usageService.checkLimitSet(
orgId,
FeatureId.SITES,
LimitId.SITES,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
@@ -274,7 +274,7 @@ export async function registerNewt(
)
);
await usageService.add(orgId, FeatureId.SITES, 1, trx);
await usageService.add(orgId, LimitId.SITES, 1, trx);
});
} finally {
await releaseSubnetLock();

View File

@@ -27,7 +27,7 @@ import { OpenAPITags, registry } from "@server/openApi";
import { isValidCIDR } from "@server/lib/validators";
import { createCustomer } from "#dynamic/lib/billing";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId, limitsService, freeLimitSet } from "@server/lib/billing";
import { LimitId, limitsService, freeLimitSet } from "@server/lib/billing";
import { build } from "@server/build";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { doCidrsOverlap } from "@server/lib/ip";
@@ -202,7 +202,7 @@ export async function createOrg(
if (build == "saas" && billingOrgIdForNewOrg) {
const usage = await usageService.getUsage(
billingOrgIdForNewOrg,
FeatureId.ORGINIZATIONS
LimitId.ORGINIZATIONS
);
if (!usage) {
return next(
@@ -214,7 +214,7 @@ export async function createOrg(
}
const rejectOrgs = await usageService.checkLimitSet(
billingOrgIdForNewOrg,
FeatureId.ORGINIZATIONS,
LimitId.ORGINIZATIONS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
@@ -421,7 +421,7 @@ export async function createOrg(
if (customerId) {
await usageService.updateCount(
orgId,
FeatureId.USERS,
LimitId.USERS,
1,
customerId
); // Only 1 because we are creating the org
@@ -431,7 +431,7 @@ export async function createOrg(
if (numOrgs) {
usageService.updateCount(
billingOrgIdForNewOrg || orgId,
FeatureId.ORGINIZATIONS,
LimitId.ORGINIZATIONS,
numOrgs
);
}

View File

@@ -19,7 +19,7 @@ import { getNextAvailableClientSubnet, isIpInCidr } from "@server/lib/ip";
import { verifyExitNodeOrgAccess } from "#dynamic/lib/exitNodes";
import { build } from "@server/build";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { generateId } from "@server/auth/sessions/app";
const createSiteParamsSchema = z.strictObject({
@@ -160,7 +160,7 @@ export async function createSite(
}
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.SITES);
const usage = await usageService.getUsage(orgId, LimitId.SITES);
if (!usage) {
return next(
createHttpError(
@@ -172,7 +172,7 @@ export async function createSite(
const rejectSites = await usageService.checkLimitSet(
orgId,
FeatureId.SITES,
LimitId.SITES,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1
@@ -519,7 +519,7 @@ export async function createSite(
});
}
await usageService.add(orgId, FeatureId.SITES, 1, trx);
await usageService.add(orgId, LimitId.SITES, 1, trx);
});
} finally {
await releaseSubnetLock?.();

View File

@@ -13,7 +13,7 @@ import { sendToClient } from "#dynamic/routers/ws";
import { OpenAPITags, registry } from "@server/openApi";
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
import {
deleteAssociatedResourcesForSite,
@@ -177,7 +177,7 @@ export async function deleteSite(
}
await trx.delete(sites).where(eq(sites.siteId, siteId));
await usageService.add(site.orgId, FeatureId.SITES, -1, trx);
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
});
if (deleteResources) {

View File

@@ -17,7 +17,7 @@ import { fromError } from "zod-validation-error";
import { checkValidInvite } from "@server/auth/checkValidInvite";
import { verifySession } from "@server/auth/sessions/verifySession";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { build } from "@server/build";
import { assignUserToOrg } from "@server/lib/userOrg";
@@ -104,7 +104,7 @@ export async function acceptInvite(
if (build == "saas") {
const usage = await usageService.getUsage(
existingInvite.orgId,
FeatureId.USERS
LimitId.USERS
);
if (!usage) {
return next(
@@ -117,7 +117,7 @@ export async function acceptInvite(
const rejectUsers = await usageService.checkLimitSet(
existingInvite.orgId,
FeatureId.USERS,
LimitId.USERS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1

View File

@@ -12,7 +12,7 @@ import { and, eq, inArray } from "drizzle-orm";
import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db";
import { generateId } from "@server/auth/sessions/app";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { build } from "@server/build";
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
import { isSubscribed } from "#dynamic/lib/isSubscribed";
@@ -123,7 +123,7 @@ export async function createOrgUser(
} = parsedBody.data;
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
const usage = await usageService.getUsage(orgId, LimitId.USERS);
if (!usage) {
return next(
createHttpError(
@@ -135,7 +135,7 @@ export async function createOrgUser(
const rejectUsers = await usageService.checkLimitSet(
orgId,
FeatureId.USERS,
LimitId.USERS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1

View File

@@ -25,7 +25,7 @@ import SendInviteLink from "@server/emails/templates/SendInviteLink";
import { OpenAPITags, registry } from "@server/openApi";
import { UserType } from "@server/types/UserTypes";
import { usageService } from "@server/lib/billing/usageService";
import { FeatureId } from "@server/lib/billing";
import { LimitId } from "@server/lib/billing";
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
import { build } from "@server/build";
import cache from "#dynamic/lib/cache";
@@ -73,7 +73,6 @@ const InviteUserResponseDataSchema = z.object({
expiresAt: z.number()
});
registry.registerPath({
method: "post",
path: "/org/{orgId}/create-invite",
@@ -94,7 +93,9 @@ registry.registerPath({
description: "Successful response",
content: {
"application/json": {
schema: createApiResponseSchema(InviteUserResponseDataSchema)
schema: createApiResponseSchema(
InviteUserResponseDataSchema
)
}
}
}
@@ -181,7 +182,7 @@ export async function inviteUser(
}
if (build == "saas") {
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
const usage = await usageService.getUsage(orgId, LimitId.USERS);
if (!usage) {
return next(
createHttpError(
@@ -192,7 +193,7 @@ export async function inviteUser(
}
const rejectUsers = await usageService.checkLimitSet(
orgId,
FeatureId.USERS,
LimitId.USERS,
{
...usage,
instantaneousValue: (usage.instantaneousValue || 0) + 1