mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-11 16:32:10 +02:00
Rename to limit id
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
export enum FeatureId {
|
export enum LimitId {
|
||||||
USERS = "users",
|
USERS = "users",
|
||||||
SITES = "sites",
|
SITES = "sites",
|
||||||
EGRESS_DATA_MB = "egressDataMb",
|
EGRESS_DATA_MB = "egressDataMb",
|
||||||
@@ -8,21 +8,23 @@ export enum FeatureId {
|
|||||||
TIER1 = "tier1"
|
TIER1 = "tier1"
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFeatureDisplayName(featureId: FeatureId): Promise<string> {
|
export async function getFeatureDisplayName(
|
||||||
|
featureId: LimitId
|
||||||
|
): Promise<string> {
|
||||||
switch (featureId) {
|
switch (featureId) {
|
||||||
case FeatureId.USERS:
|
case LimitId.USERS:
|
||||||
return "Users";
|
return "Users";
|
||||||
case FeatureId.SITES:
|
case LimitId.SITES:
|
||||||
return "Sites";
|
return "Sites";
|
||||||
case FeatureId.EGRESS_DATA_MB:
|
case LimitId.EGRESS_DATA_MB:
|
||||||
return "Egress Data (MB)";
|
return "Egress Data (MB)";
|
||||||
case FeatureId.DOMAINS:
|
case LimitId.DOMAINS:
|
||||||
return "Domains";
|
return "Domains";
|
||||||
case FeatureId.REMOTE_EXIT_NODES:
|
case LimitId.REMOTE_EXIT_NODES:
|
||||||
return "Remote Exit Nodes";
|
return "Remote Exit Nodes";
|
||||||
case FeatureId.ORGINIZATIONS:
|
case LimitId.ORGINIZATIONS:
|
||||||
return "Organizations";
|
return "Organizations";
|
||||||
case FeatureId.TIER1:
|
case LimitId.TIER1:
|
||||||
return "Home Lab";
|
return "Home Lab";
|
||||||
default:
|
default:
|
||||||
return featureId;
|
return featureId;
|
||||||
@@ -30,15 +32,16 @@ export async function getFeatureDisplayName(featureId: FeatureId): Promise<strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is from the old system
|
// 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"
|
// [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"
|
// [FeatureId.EGRESS_DATA_MB]: "mtr_test_61Snh2a2m6qome5Kv41DCpkOb237B3dQ"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getFeatureMeterId(featureId: FeatureId): string | undefined {
|
export function getFeatureMeterId(featureId: LimitId): string | undefined {
|
||||||
if (
|
if (
|
||||||
process.env.ENVIRONMENT == "prod" &&
|
process.env.ENVIRONMENT == "prod" &&
|
||||||
process.env.SANDBOX_MODE !== "true"
|
process.env.SANDBOX_MODE !== "true"
|
||||||
@@ -49,22 +52,20 @@ export function getFeatureMeterId(featureId: FeatureId): string | undefined {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFeatureIdByMetricId(
|
export function getFeatureIdByMetricId(metricId: string): LimitId | undefined {
|
||||||
metricId: string
|
return (Object.entries(FeatureMeterIds) as [LimitId, string][]).find(
|
||||||
): FeatureId | undefined {
|
|
||||||
return (Object.entries(FeatureMeterIds) as [FeatureId, string][]).find(
|
|
||||||
([_, v]) => v === metricId
|
([_, v]) => v === metricId
|
||||||
)?.[0];
|
)?.[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FeaturePriceSet = Partial<Record<FeatureId, string>>;
|
export type FeaturePriceSet = Partial<Record<LimitId, string>>;
|
||||||
|
|
||||||
export const tier1FeaturePriceSet: FeaturePriceSet = {
|
export const tier1FeaturePriceSet: FeaturePriceSet = {
|
||||||
[FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
|
[LimitId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier1FeaturePriceSetSandbox: FeaturePriceSet = {
|
export const tier1FeaturePriceSetSandbox: FeaturePriceSet = {
|
||||||
[FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
|
[LimitId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getTier1FeaturePriceSet(): FeaturePriceSet {
|
export function getTier1FeaturePriceSet(): FeaturePriceSet {
|
||||||
@@ -79,11 +80,11 @@ export function getTier1FeaturePriceSet(): FeaturePriceSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const tier2FeaturePriceSet: FeaturePriceSet = {
|
export const tier2FeaturePriceSet: FeaturePriceSet = {
|
||||||
[FeatureId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
|
[LimitId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier2FeaturePriceSetSandbox: FeaturePriceSet = {
|
export const tier2FeaturePriceSetSandbox: FeaturePriceSet = {
|
||||||
[FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
|
[LimitId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getTier2FeaturePriceSet(): FeaturePriceSet {
|
export function getTier2FeaturePriceSet(): FeaturePriceSet {
|
||||||
@@ -98,11 +99,11 @@ export function getTier2FeaturePriceSet(): FeaturePriceSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const tier3FeaturePriceSet: FeaturePriceSet = {
|
export const tier3FeaturePriceSet: FeaturePriceSet = {
|
||||||
[FeatureId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
|
[LimitId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier3FeaturePriceSetSandbox: FeaturePriceSet = {
|
export const tier3FeaturePriceSetSandbox: FeaturePriceSet = {
|
||||||
[FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
|
[LimitId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getTier3FeaturePriceSet(): FeaturePriceSet {
|
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
|
// Check all feature price sets
|
||||||
const allPriceSets = [
|
const allPriceSets = [
|
||||||
getTier1FeaturePriceSet(),
|
getTier1FeaturePriceSet(),
|
||||||
@@ -125,7 +126,7 @@ export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
|
|||||||
];
|
];
|
||||||
|
|
||||||
for (const priceSet of allPriceSets) {
|
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
|
([_, price]) => price === priceId
|
||||||
);
|
);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
import { FeatureId, FeaturePriceSet } from "./features";
|
import { LimitId, FeaturePriceSet } from "./features";
|
||||||
import { usageService } from "./usageService";
|
import { usageService } from "./usageService";
|
||||||
|
|
||||||
export async function getLineItems(
|
export async function getLineItems(
|
||||||
featurePriceSet: FeaturePriceSet,
|
featurePriceSet: FeaturePriceSet,
|
||||||
orgId: string,
|
orgId: string
|
||||||
): Promise<Stripe.Checkout.SessionCreateParams.LineItem[]> {
|
): 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]) => {
|
return Object.entries(featurePriceSet).map(([featureId, priceId]) => {
|
||||||
let quantity: number | undefined;
|
let quantity: number | undefined;
|
||||||
|
|
||||||
if (featureId === FeatureId.USERS) {
|
if (featureId === LimitId.USERS) {
|
||||||
quantity = users?.instantaneousValue || 1;
|
quantity = users?.instantaneousValue || 1;
|
||||||
} else if (featureId === FeatureId.TIER1) {
|
} else if (featureId === LimitId.TIER1) {
|
||||||
quantity = 1;
|
quantity = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +1,70 @@
|
|||||||
import { FeatureId } from "./features";
|
import { LimitId } from "./features";
|
||||||
|
|
||||||
export type LimitSet = Partial<{
|
export type LimitSet = Partial<{
|
||||||
[key in FeatureId]: {
|
[key in LimitId]: {
|
||||||
value: number | null; // null indicates no limit
|
value: number | null; // null indicates no limit
|
||||||
description?: string;
|
description?: string;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export const freeLimitSet: LimitSet = {
|
export const freeLimitSet: LimitSet = {
|
||||||
[FeatureId.SITES]: { value: 5, description: "Basic limit" },
|
[LimitId.SITES]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.USERS]: { value: 5, description: "Basic limit" },
|
[LimitId.USERS]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.DOMAINS]: { value: 5, description: "Basic limit" },
|
[LimitId.DOMAINS]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
|
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
|
||||||
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Basic limit" },
|
[LimitId.ORGINIZATIONS]: { value: 1, description: "Basic limit" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier1LimitSet: LimitSet = {
|
export const tier1LimitSet: LimitSet = {
|
||||||
[FeatureId.USERS]: { value: 7, description: "Home limit" },
|
[LimitId.USERS]: { value: 7, description: "Home limit" },
|
||||||
[FeatureId.SITES]: { value: 10, description: "Home limit" },
|
[LimitId.SITES]: { value: 10, description: "Home limit" },
|
||||||
[FeatureId.DOMAINS]: { value: 10, description: "Home limit" },
|
[LimitId.DOMAINS]: { value: 10, description: "Home limit" },
|
||||||
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
|
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
|
||||||
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Home limit" },
|
[LimitId.ORGINIZATIONS]: { value: 1, description: "Home limit" }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier2LimitSet: LimitSet = {
|
export const tier2LimitSet: LimitSet = {
|
||||||
[FeatureId.USERS]: {
|
[LimitId.USERS]: {
|
||||||
value: 50,
|
value: 50,
|
||||||
description: "Team limit"
|
description: "Team limit"
|
||||||
},
|
},
|
||||||
[FeatureId.SITES]: {
|
[LimitId.SITES]: {
|
||||||
value: 50,
|
value: 50,
|
||||||
description: "Team limit"
|
description: "Team limit"
|
||||||
},
|
},
|
||||||
[FeatureId.DOMAINS]: {
|
[LimitId.DOMAINS]: {
|
||||||
value: 50,
|
value: 50,
|
||||||
description: "Team limit"
|
description: "Team limit"
|
||||||
},
|
},
|
||||||
[FeatureId.REMOTE_EXIT_NODES]: {
|
[LimitId.REMOTE_EXIT_NODES]: {
|
||||||
value: 3,
|
value: 3,
|
||||||
description: "Team limit"
|
description: "Team limit"
|
||||||
},
|
},
|
||||||
[FeatureId.ORGINIZATIONS]: {
|
[LimitId.ORGINIZATIONS]: {
|
||||||
value: 1,
|
value: 1,
|
||||||
description: "Team limit"
|
description: "Team limit"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier3LimitSet: LimitSet = {
|
export const tier3LimitSet: LimitSet = {
|
||||||
[FeatureId.USERS]: {
|
[LimitId.USERS]: {
|
||||||
value: 250,
|
value: 250,
|
||||||
description: "Business limit"
|
description: "Business limit"
|
||||||
},
|
},
|
||||||
[FeatureId.SITES]: {
|
[LimitId.SITES]: {
|
||||||
value: 250,
|
value: 250,
|
||||||
description: "Business limit"
|
description: "Business limit"
|
||||||
},
|
},
|
||||||
[FeatureId.DOMAINS]: {
|
[LimitId.DOMAINS]: {
|
||||||
value: 100,
|
value: 100,
|
||||||
description: "Business limit"
|
description: "Business limit"
|
||||||
},
|
},
|
||||||
[FeatureId.REMOTE_EXIT_NODES]: {
|
[LimitId.REMOTE_EXIT_NODES]: {
|
||||||
value: 20,
|
value: 20,
|
||||||
description: "Business limit"
|
description: "Business limit"
|
||||||
},
|
},
|
||||||
[FeatureId.ORGINIZATIONS]: {
|
[LimitId.ORGINIZATIONS]: {
|
||||||
value: 5,
|
value: 5,
|
||||||
description: "Business limit"
|
description: "Business limit"
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db, limits } from "@server/db";
|
import { db, limits } from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { LimitSet } from "./limitSet";
|
import { LimitSet } from "./limitSet";
|
||||||
import { FeatureId } from "./features";
|
import { LimitId } from "./features";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
class LimitService {
|
class LimitService {
|
||||||
@@ -38,7 +38,7 @@ class LimitService {
|
|||||||
|
|
||||||
async getOrgLimit(
|
async getOrgLimit(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId: FeatureId
|
featureId: LimitId
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const limitId = `${orgId}-${featureId}`;
|
const limitId = `${orgId}-${featureId}`;
|
||||||
const [limit] = await db
|
const [limit] = await db
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Transaction,
|
Transaction,
|
||||||
orgs
|
orgs
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { FeatureId, getFeatureMeterId } from "./features";
|
import { LimitId, getFeatureMeterId } from "./features";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { regionalCache as cache } from "#dynamic/lib/cache";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
@@ -37,7 +37,7 @@ export class UsageService {
|
|||||||
|
|
||||||
public async add(
|
public async add(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId: FeatureId,
|
featureId: LimitId,
|
||||||
value: number,
|
value: number,
|
||||||
transaction: any = null
|
transaction: any = null
|
||||||
): Promise<Usage | null> {
|
): Promise<Usage | null> {
|
||||||
@@ -114,7 +114,7 @@ export class UsageService {
|
|||||||
|
|
||||||
private async internalAddUsage(
|
private async internalAddUsage(
|
||||||
orgId: string, // here the orgId is the billing org already resolved by getBillingOrg in updateCount
|
orgId: string, // here the orgId is the billing org already resolved by getBillingOrg in updateCount
|
||||||
featureId: FeatureId,
|
featureId: LimitId,
|
||||||
value: number,
|
value: number,
|
||||||
trx: Transaction
|
trx: Transaction
|
||||||
): Promise<Usage> {
|
): Promise<Usage> {
|
||||||
@@ -163,7 +163,7 @@ export class UsageService {
|
|||||||
|
|
||||||
async updateCount(
|
async updateCount(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId: FeatureId,
|
featureId: LimitId,
|
||||||
value?: number,
|
value?: number,
|
||||||
customerId?: string
|
customerId?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
@@ -227,7 +227,7 @@ export class UsageService {
|
|||||||
|
|
||||||
private async getCustomerId(
|
private async getCustomerId(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId: FeatureId
|
featureId: LimitId
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
const orgIdToUse = await this.getBillingOrg(orgId);
|
const orgIdToUse = await this.getBillingOrg(orgId);
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ export class UsageService {
|
|||||||
|
|
||||||
public async getUsage(
|
public async getUsage(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId: FeatureId,
|
featureId: LimitId,
|
||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<Usage | null> {
|
): Promise<Usage | null> {
|
||||||
if (noop()) {
|
if (noop()) {
|
||||||
@@ -376,7 +376,7 @@ export class UsageService {
|
|||||||
|
|
||||||
public async checkLimitSet(
|
public async checkLimitSet(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
featureId?: FeatureId,
|
featureId?: LimitId,
|
||||||
usage?: Usage,
|
usage?: Usage,
|
||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
@@ -424,7 +424,7 @@ export class UsageService {
|
|||||||
} else {
|
} else {
|
||||||
currentUsage = await this.getUsage(
|
currentUsage = await this.getUsage(
|
||||||
orgIdToUse,
|
orgIdToUse,
|
||||||
limit.featureId as FeatureId,
|
limit.featureId as LimitId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { deletePeer } from "@server/routers/gerbil/peers";
|
|||||||
import { OlmErrorCodes } from "@server/routers/olm/error";
|
import { OlmErrorCodes } from "@server/routers/olm/error";
|
||||||
import { sendTerminateClient } from "@server/routers/client/terminate";
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
import { usageService } from "./billing/usageService";
|
import { usageService } from "./billing/usageService";
|
||||||
import { FeatureId } from "./billing";
|
import { LimitId } from "./billing";
|
||||||
|
|
||||||
export type DeleteOrgByIdResult = {
|
export type DeleteOrgByIdResult = {
|
||||||
deletedNewtIds: string[];
|
deletedNewtIds: string[];
|
||||||
@@ -140,7 +140,9 @@ export async function deleteOrgById(
|
|||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(orgDomains)
|
.from(orgDomains)
|
||||||
.where(eq(orgDomains.domainId, domainId));
|
.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) {
|
if (orgCount.count === 1) {
|
||||||
domainIdsToDelete.push(domainId);
|
domainIdsToDelete.push(domainId);
|
||||||
}
|
}
|
||||||
@@ -152,7 +154,7 @@ export async function deleteOrgById(
|
|||||||
.where(inArray(domains.domainId, domainIdsToDelete));
|
.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));
|
await trx.delete(orgs).where(eq(orgs.orgId, orgId));
|
||||||
|
|
||||||
@@ -199,22 +201,22 @@ export async function deleteOrgById(
|
|||||||
if (org.billingOrgId) {
|
if (org.billingOrgId) {
|
||||||
usageService.updateCount(
|
usageService.updateCount(
|
||||||
org.billingOrgId,
|
org.billingOrgId,
|
||||||
FeatureId.DOMAINS,
|
LimitId.DOMAINS,
|
||||||
domainCount ?? 0
|
domainCount ?? 0
|
||||||
);
|
);
|
||||||
usageService.updateCount(
|
usageService.updateCount(
|
||||||
org.billingOrgId,
|
org.billingOrgId,
|
||||||
FeatureId.SITES,
|
LimitId.SITES,
|
||||||
siteCount ?? 0
|
siteCount ?? 0
|
||||||
);
|
);
|
||||||
usageService.updateCount(
|
usageService.updateCount(
|
||||||
org.billingOrgId,
|
org.billingOrgId,
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
userCount ?? 0
|
userCount ?? 0
|
||||||
);
|
);
|
||||||
usageService.updateCount(
|
usageService.updateCount(
|
||||||
org.billingOrgId,
|
org.billingOrgId,
|
||||||
FeatureId.REMOTE_EXIT_NODES,
|
LimitId.REMOTE_EXIT_NODES,
|
||||||
remoteExitNodeCount ?? 0
|
remoteExitNodeCount ?? 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { eq, and, inArray, ne, exists } from "drizzle-orm";
|
import { eq, and, inArray, ne, exists } from "drizzle-orm";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { FeatureId } from "@server/lib/billing";
|
import { LimitId } from "@server/lib/billing";
|
||||||
|
|
||||||
export async function assignUserToOrg(
|
export async function assignUserToOrg(
|
||||||
org: Org,
|
org: Org,
|
||||||
@@ -61,7 +61,7 @@ export async function assignUserToOrg(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) {
|
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) {
|
if (orgsInBillingDomainThatTheUserIsStillIn.length === 0) {
|
||||||
await usageService.add(org.orgId, FeatureId.USERS, -1, trx);
|
await usageService.add(org.orgId, LimitId.USERS, -1, trx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
getTier1FeaturePriceSet,
|
getTier1FeaturePriceSet,
|
||||||
getTier3FeaturePriceSet,
|
getTier3FeaturePriceSet,
|
||||||
getTier2FeaturePriceSet,
|
getTier2FeaturePriceSet,
|
||||||
FeatureId,
|
LimitId,
|
||||||
type FeaturePriceSet
|
type FeaturePriceSet
|
||||||
} from "@server/lib/billing";
|
} from "@server/lib/billing";
|
||||||
import { getLineItems } from "@server/lib/billing/getLineItems";
|
import { getLineItems } from "@server/lib/billing/getLineItems";
|
||||||
@@ -214,7 +214,7 @@ export async function changeTier(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Map to the corresponding feature in the new tier
|
// Map to the corresponding feature in the new tier
|
||||||
const newPriceId = targetPriceSet[FeatureId.USERS];
|
const newPriceId = targetPriceSet[LimitId.USERS];
|
||||||
|
|
||||||
if (newPriceId) {
|
if (newPriceId) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { fromZodError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { Limit, limits, Usage, usage } from "@server/db";
|
import { Limit, limits, Usage, usage } from "@server/db";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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";
|
import { GetOrgUsageResponse } from "@server/routers/billing/types";
|
||||||
|
|
||||||
const getOrgSchema = z.strictObject({
|
const getOrgSchema = z.strictObject({
|
||||||
@@ -93,16 +93,16 @@ export async function getOrgUsage(
|
|||||||
// Get usage for org
|
// Get usage for org
|
||||||
const usageData = [];
|
const usageData = [];
|
||||||
|
|
||||||
const sites = await usageService.getUsage(orgId, FeatureId.SITES);
|
const sites = await usageService.getUsage(orgId, LimitId.SITES);
|
||||||
const users = await usageService.getUsage(orgId, FeatureId.USERS);
|
const users = await usageService.getUsage(orgId, LimitId.USERS);
|
||||||
const domains = await usageService.getUsage(orgId, FeatureId.DOMAINS);
|
const domains = await usageService.getUsage(orgId, LimitId.DOMAINS);
|
||||||
const remoteExitNodes = await usageService.getUsage(
|
const remoteExitNodes = await usageService.getUsage(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.REMOTE_EXIT_NODES
|
LimitId.REMOTE_EXIT_NODES
|
||||||
);
|
);
|
||||||
const organizations = await usageService.getUsage(
|
const organizations = await usageService.getUsage(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.ORGINIZATIONS
|
LimitId.ORGINIZATIONS
|
||||||
);
|
);
|
||||||
// const egressData = await usageService.getUsage(
|
// const egressData = await usageService.getUsage(
|
||||||
// orgId,
|
// orgId,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import logger from "@server/logger";
|
|||||||
import { and, eq, inArray, ne } from "drizzle-orm";
|
import { and, eq, inArray, ne } from "drizzle-orm";
|
||||||
import { getNextAvailableSubnet } from "@server/lib/exitNodes";
|
import { getNextAvailableSubnet } from "@server/lib/exitNodes";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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";
|
import { CreateRemoteExitNodeResponse } from "@server/routers/remoteExitNode/types";
|
||||||
|
|
||||||
export const paramsSchema = z.object({
|
export const paramsSchema = z.object({
|
||||||
@@ -79,7 +79,10 @@ export async function createRemoteExitNode(
|
|||||||
|
|
||||||
const { remoteExitNodeId, secret } = parsedBody.data;
|
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(
|
return next(
|
||||||
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
|
createHttpError(HttpCode.FORBIDDEN, "User does not have a role")
|
||||||
);
|
);
|
||||||
@@ -87,13 +90,13 @@ export async function createRemoteExitNode(
|
|||||||
|
|
||||||
const usage = await usageService.getUsage(
|
const usage = await usageService.getUsage(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.REMOTE_EXIT_NODES
|
LimitId.REMOTE_EXIT_NODES
|
||||||
);
|
);
|
||||||
if (usage) {
|
if (usage) {
|
||||||
const rejectRemoteExitNodes = await usageService.checkLimitSet(
|
const rejectRemoteExitNodes = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
|
|
||||||
FeatureId.REMOTE_EXIT_NODES,
|
LimitId.REMOTE_EXIT_NODES,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
@@ -264,7 +267,7 @@ export async function createRemoteExitNode(
|
|||||||
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
|
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
|
||||||
await usageService.add(
|
await usageService.add(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.REMOTE_EXIT_NODES,
|
LimitId.REMOTE_EXIT_NODES,
|
||||||
1,
|
1,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import createHttpError from "http-errors";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { FeatureId } from "@server/lib/billing";
|
import { LimitId } from "@server/lib/billing";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string().min(1),
|
orgId: z.string().min(1),
|
||||||
@@ -117,7 +117,7 @@ export async function deleteRemoteExitNode(
|
|||||||
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
|
if (orgsInBillingDomainThatTheNodeIsStillIn.length === 0) {
|
||||||
await usageService.add(
|
await usageService.add(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.REMOTE_EXIT_NODES,
|
LimitId.REMOTE_EXIT_NODES,
|
||||||
-1,
|
-1,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { getOrgTierData } from "#dynamic/lib/billing";
|
|||||||
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
|
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { FeatureId } from "@server/lib/billing";
|
import { LimitId } from "@server/lib/billing";
|
||||||
|
|
||||||
const deleteMyAccountBody = z.strictObject({
|
const deleteMyAccountBody = z.strictObject({
|
||||||
password: z.string().optional(),
|
password: z.string().optional(),
|
||||||
@@ -220,7 +220,7 @@ export async function deleteMyAccount(
|
|||||||
await trx.delete(users).where(eq(users.userId, userId));
|
await trx.delete(users).where(eq(users.userId, userId));
|
||||||
// loop through the other orgs and decrement the count
|
// loop through the other orgs and decrement the count
|
||||||
for (const userOrg of otherOrgsTheUserWasIn) {
|
for (const userOrg of otherOrgsTheUserWasIn) {
|
||||||
await usageService.add(userOrg.orgId, FeatureId.USERS, -1, trx);
|
await usageService.add(userOrg.orgId, LimitId.USERS, -1, trx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { subdomainSchema } from "@server/lib/schemas";
|
|||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { isSecondLevelDomain, isValidDomain } from "@server/lib/validators";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
@@ -120,7 +120,7 @@ export async function createOrgDomain(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(orgId, FeatureId.DOMAINS);
|
const usage = await usageService.getUsage(orgId, LimitId.DOMAINS);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -132,7 +132,7 @@ export async function createOrgDomain(
|
|||||||
const rejectDomains = await usageService.checkLimitSet(
|
const rejectDomains = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
|
|
||||||
FeatureId.DOMAINS,
|
LimitId.DOMAINS,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
@@ -346,7 +346,7 @@ export async function createOrgDomain(
|
|||||||
await trx.insert(dnsRecords).values(recordsToInsert);
|
await trx.insert(dnsRecords).values(recordsToInsert);
|
||||||
}
|
}
|
||||||
|
|
||||||
await usageService.add(orgId, FeatureId.DOMAINS, 1, trx);
|
await usageService.add(orgId, LimitId.DOMAINS, 1, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!returned) {
|
if (!returned) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { FeatureId } from "@server/lib/billing";
|
import { LimitId } from "@server/lib/billing";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
domainId: z.string(),
|
domainId: z.string(),
|
||||||
@@ -77,7 +77,7 @@ export async function deleteAccountDomain(
|
|||||||
|
|
||||||
await trx.delete(domains).where(eq(domains.domainId, domainId));
|
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, {
|
return response<DeleteAccountDomainResponse>(res, {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import createHttpError from "http-errors";
|
|||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { checkExitNodeOrg } from "#dynamic/lib/exitNodes";
|
||||||
import { build } from "@server/build";
|
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.
|
// PostgreSQL: batch UPDATE … FROM (VALUES …) - single round-trip per chunk.
|
||||||
const valuesList = chunk.map(([publicKey, { bytesIn, bytesOut }]) =>
|
const valuesList = chunk.map(
|
||||||
sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)`
|
([publicKey, { bytesIn, bytesOut }]) =>
|
||||||
|
sql`(${publicKey}::text, ${bytesIn}::real, ${bytesOut}::real)`
|
||||||
);
|
);
|
||||||
const valuesClause = sql.join(valuesList, sql`, `);
|
const valuesClause = sql.join(valuesList, sql`, `);
|
||||||
return dbQueryRows<{ orgId: string; pubKey: string }>(sql`
|
return dbQueryRows<{ orgId: string; pubKey: string }>(sql`
|
||||||
@@ -228,7 +229,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
|
|||||||
const totalBandwidth = orgUsageMap.get(orgId)!;
|
const totalBandwidth = orgUsageMap.get(orgId)!;
|
||||||
const bandwidthUsage = await usageService.add(
|
const bandwidthUsage = await usageService.add(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.EGRESS_DATA_MB,
|
LimitId.EGRESS_DATA_MB,
|
||||||
totalBandwidth
|
totalBandwidth
|
||||||
);
|
);
|
||||||
if (bandwidthUsage) {
|
if (bandwidthUsage) {
|
||||||
@@ -236,7 +237,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
|
|||||||
usageService
|
usageService
|
||||||
.checkLimitSet(
|
.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.EGRESS_DATA_MB,
|
LimitId.EGRESS_DATA_MB,
|
||||||
bandwidthUsage
|
bandwidthUsage
|
||||||
)
|
)
|
||||||
.catch((error: any) => {
|
.catch((error: any) => {
|
||||||
@@ -247,10 +248,7 @@ export async function flushSiteBandwidthToDb(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(`Error processing usage for org ${orgId}:`, error);
|
||||||
`Error processing usage for org ${orgId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
// Continue with other orgs.
|
// Continue with other orgs.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
} from "@server/auth/sessions/app";
|
} from "@server/auth/sessions/app";
|
||||||
import { decrypt } from "@server/lib/crypto";
|
import { decrypt } from "@server/lib/crypto";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
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 { usageService } from "@server/lib/billing/usageService";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
||||||
@@ -645,7 +645,7 @@ export async function validateOidcCallback(
|
|||||||
for (const orgCount of orgUserCounts) {
|
for (const orgCount of orgUserCounts) {
|
||||||
await usageService.updateCount(
|
await usageService.updateCount(
|
||||||
orgCount.orgId,
|
orgCount.orgId,
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
orgCount.userCount
|
orgCount.userCount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { getUniqueSiteName } from "@server/db/names";
|
|||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { INSPECT_MAX_BYTES } from "buffer";
|
||||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ export async function registerNewt(
|
|||||||
|
|
||||||
// SaaS billing check
|
// SaaS billing check
|
||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(orgId, FeatureId.SITES);
|
const usage = await usageService.getUsage(orgId, LimitId.SITES);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -180,7 +180,7 @@ export async function registerNewt(
|
|||||||
}
|
}
|
||||||
const rejectSites = await usageService.checkLimitSet(
|
const rejectSites = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.SITES,
|
LimitId.SITES,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
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 {
|
} finally {
|
||||||
await releaseSubnetLock();
|
await releaseSubnetLock();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
|||||||
import { isValidCIDR } from "@server/lib/validators";
|
import { isValidCIDR } from "@server/lib/validators";
|
||||||
import { createCustomer } from "#dynamic/lib/billing";
|
import { createCustomer } from "#dynamic/lib/billing";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { build } from "@server/build";
|
||||||
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
||||||
import { doCidrsOverlap } from "@server/lib/ip";
|
import { doCidrsOverlap } from "@server/lib/ip";
|
||||||
@@ -202,7 +202,7 @@ export async function createOrg(
|
|||||||
if (build == "saas" && billingOrgIdForNewOrg) {
|
if (build == "saas" && billingOrgIdForNewOrg) {
|
||||||
const usage = await usageService.getUsage(
|
const usage = await usageService.getUsage(
|
||||||
billingOrgIdForNewOrg,
|
billingOrgIdForNewOrg,
|
||||||
FeatureId.ORGINIZATIONS
|
LimitId.ORGINIZATIONS
|
||||||
);
|
);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
@@ -214,7 +214,7 @@ export async function createOrg(
|
|||||||
}
|
}
|
||||||
const rejectOrgs = await usageService.checkLimitSet(
|
const rejectOrgs = await usageService.checkLimitSet(
|
||||||
billingOrgIdForNewOrg,
|
billingOrgIdForNewOrg,
|
||||||
FeatureId.ORGINIZATIONS,
|
LimitId.ORGINIZATIONS,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
@@ -421,7 +421,7 @@ export async function createOrg(
|
|||||||
if (customerId) {
|
if (customerId) {
|
||||||
await usageService.updateCount(
|
await usageService.updateCount(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
1,
|
1,
|
||||||
customerId
|
customerId
|
||||||
); // Only 1 because we are creating the org
|
); // Only 1 because we are creating the org
|
||||||
@@ -431,7 +431,7 @@ export async function createOrg(
|
|||||||
if (numOrgs) {
|
if (numOrgs) {
|
||||||
usageService.updateCount(
|
usageService.updateCount(
|
||||||
billingOrgIdForNewOrg || orgId,
|
billingOrgIdForNewOrg || orgId,
|
||||||
FeatureId.ORGINIZATIONS,
|
LimitId.ORGINIZATIONS,
|
||||||
numOrgs
|
numOrgs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { getNextAvailableClientSubnet, isIpInCidr } from "@server/lib/ip";
|
|||||||
import { verifyExitNodeOrgAccess } from "#dynamic/lib/exitNodes";
|
import { verifyExitNodeOrgAccess } from "#dynamic/lib/exitNodes";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
|
|
||||||
const createSiteParamsSchema = z.strictObject({
|
const createSiteParamsSchema = z.strictObject({
|
||||||
@@ -160,7 +160,7 @@ export async function createSite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(orgId, FeatureId.SITES);
|
const usage = await usageService.getUsage(orgId, LimitId.SITES);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -172,7 +172,7 @@ export async function createSite(
|
|||||||
const rejectSites = await usageService.checkLimitSet(
|
const rejectSites = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
|
|
||||||
FeatureId.SITES,
|
LimitId.SITES,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
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 {
|
} finally {
|
||||||
await releaseSubnetLock?.();
|
await releaseSubnetLock?.();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { sendToClient } from "#dynamic/routers/ws";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
|
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
|
||||||
import {
|
import {
|
||||||
deleteAssociatedResourcesForSite,
|
deleteAssociatedResourcesForSite,
|
||||||
@@ -177,7 +177,7 @@ export async function deleteSite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await trx.delete(sites).where(eq(sites.siteId, siteId));
|
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) {
|
if (deleteResources) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { checkValidInvite } from "@server/auth/checkValidInvite";
|
import { checkValidInvite } from "@server/auth/checkValidInvite";
|
||||||
import { verifySession } from "@server/auth/sessions/verifySession";
|
import { verifySession } from "@server/auth/sessions/verifySession";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { assignUserToOrg } from "@server/lib/userOrg";
|
import { assignUserToOrg } from "@server/lib/userOrg";
|
||||||
@@ -104,7 +104,7 @@ export async function acceptInvite(
|
|||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(
|
const usage = await usageService.getUsage(
|
||||||
existingInvite.orgId,
|
existingInvite.orgId,
|
||||||
FeatureId.USERS
|
LimitId.USERS
|
||||||
);
|
);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
@@ -117,7 +117,7 @@ export async function acceptInvite(
|
|||||||
const rejectUsers = await usageService.checkLimitSet(
|
const rejectUsers = await usageService.checkLimitSet(
|
||||||
existingInvite.orgId,
|
existingInvite.orgId,
|
||||||
|
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { and, eq, inArray } from "drizzle-orm";
|
|||||||
import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db";
|
import { idp, idpOidcConfig, roles, userOrgs, users } from "@server/db";
|
||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { build } from "@server/build";
|
||||||
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
||||||
@@ -123,7 +123,7 @@ export async function createOrgUser(
|
|||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
|
const usage = await usageService.getUsage(orgId, LimitId.USERS);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -135,7 +135,7 @@ export async function createOrgUser(
|
|||||||
const rejectUsers = await usageService.checkLimitSet(
|
const rejectUsers = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
|
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import SendInviteLink from "@server/emails/templates/SendInviteLink";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
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 { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import cache from "#dynamic/lib/cache";
|
import cache from "#dynamic/lib/cache";
|
||||||
@@ -73,7 +73,6 @@ const InviteUserResponseDataSchema = z.object({
|
|||||||
expiresAt: z.number()
|
expiresAt: z.number()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "post",
|
method: "post",
|
||||||
path: "/org/{orgId}/create-invite",
|
path: "/org/{orgId}/create-invite",
|
||||||
@@ -94,7 +93,9 @@ registry.registerPath({
|
|||||||
description: "Successful response",
|
description: "Successful response",
|
||||||
content: {
|
content: {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
schema: createApiResponseSchema(InviteUserResponseDataSchema)
|
schema: createApiResponseSchema(
|
||||||
|
InviteUserResponseDataSchema
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,7 +182,7 @@ export async function inviteUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (build == "saas") {
|
if (build == "saas") {
|
||||||
const usage = await usageService.getUsage(orgId, FeatureId.USERS);
|
const usage = await usageService.getUsage(orgId, LimitId.USERS);
|
||||||
if (!usage) {
|
if (!usage) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -192,7 +193,7 @@ export async function inviteUser(
|
|||||||
}
|
}
|
||||||
const rejectUsers = await usageService.checkLimitSet(
|
const rejectUsers = await usageService.checkLimitSet(
|
||||||
orgId,
|
orgId,
|
||||||
FeatureId.USERS,
|
LimitId.USERS,
|
||||||
{
|
{
|
||||||
...usage,
|
...usage,
|
||||||
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
instantaneousValue: (usage.instantaneousValue || 0) + 1
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import {
|
|||||||
tier2LimitSet,
|
tier2LimitSet,
|
||||||
tier3LimitSet
|
tier3LimitSet
|
||||||
} from "@server/lib/billing/limitSet";
|
} from "@server/lib/billing/limitSet";
|
||||||
import { FeatureId } from "@server/lib/billing/features";
|
import { LimitId } from "@server/lib/billing/features";
|
||||||
import TrialBillingBanner from "@app/components/TrialBillingBanner";
|
import TrialBillingBanner from "@app/components/TrialBillingBanner";
|
||||||
|
|
||||||
// Plan tier definitions matching the mockup
|
// Plan tier definitions matching the mockup
|
||||||
@@ -161,32 +161,32 @@ const tierLimits: Record<
|
|||||||
}
|
}
|
||||||
> = {
|
> = {
|
||||||
basic: {
|
basic: {
|
||||||
users: freeLimitSet[FeatureId.USERS]?.value ?? 0,
|
users: freeLimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: freeLimitSet[FeatureId.SITES]?.value ?? 0,
|
sites: freeLimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: freeLimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
domains: freeLimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: freeLimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: freeLimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: freeLimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0
|
organizations: freeLimitSet[LimitId.ORGINIZATIONS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier1: {
|
tier1: {
|
||||||
users: tier1LimitSet[FeatureId.USERS]?.value ?? 0,
|
users: tier1LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier1LimitSet[FeatureId.SITES]?.value ?? 0,
|
sites: tier1LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier1LimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
domains: tier1LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier1LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier1LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier1LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0
|
organizations: tier1LimitSet[LimitId.ORGINIZATIONS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier2: {
|
tier2: {
|
||||||
users: tier2LimitSet[FeatureId.USERS]?.value ?? 0,
|
users: tier2LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier2LimitSet[FeatureId.SITES]?.value ?? 0,
|
sites: tier2LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier2LimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
domains: tier2LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier2LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier2LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier2LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0
|
organizations: tier2LimitSet[LimitId.ORGINIZATIONS]?.value ?? 0
|
||||||
},
|
},
|
||||||
tier3: {
|
tier3: {
|
||||||
users: tier3LimitSet[FeatureId.USERS]?.value ?? 0,
|
users: tier3LimitSet[LimitId.USERS]?.value ?? 0,
|
||||||
sites: tier3LimitSet[FeatureId.SITES]?.value ?? 0,
|
sites: tier3LimitSet[LimitId.SITES]?.value ?? 0,
|
||||||
domains: tier3LimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
domains: tier3LimitSet[LimitId.DOMAINS]?.value ?? 0,
|
||||||
remoteNodes: tier3LimitSet[FeatureId.REMOTE_EXIT_NODES]?.value ?? 0,
|
remoteNodes: tier3LimitSet[LimitId.REMOTE_EXIT_NODES]?.value ?? 0,
|
||||||
organizations: tier3LimitSet[FeatureId.ORGINIZATIONS]?.value ?? 0
|
organizations: tier3LimitSet[LimitId.ORGINIZATIONS]?.value ?? 0
|
||||||
},
|
},
|
||||||
enterprise: {
|
enterprise: {
|
||||||
users: 0, // Custom for enterprise
|
users: 0, // Custom for enterprise
|
||||||
|
|||||||
Reference in New Issue
Block a user