feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
45
packages/billing/src/config/features.ts
Normal file
45
packages/billing/src/config/features.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { PricingPlanType } from "../types";
|
||||
|
||||
const FREE_FEATURES = {
|
||||
SYNC: "SYNC",
|
||||
BASIC_SUPPORT: "BASIC_SUPPORT",
|
||||
LIMITED_STORAGE: "LIMITED_STORAGE",
|
||||
EMAIL_NOTIFICATIONS: "EMAIL_NOTIFICATIONS",
|
||||
BASIC_REPORTS: "BASIC_REPORTS",
|
||||
} as const;
|
||||
|
||||
const PREMIUM_FEATURES = {
|
||||
...FREE_FEATURES,
|
||||
ADVANCED_SYNC: "ADVANCED_SYNC",
|
||||
PRIORITY_SUPPORT: "PRIORITY_SUPPORT",
|
||||
MORE_STORAGE: "MORE_STORAGE",
|
||||
TEAM_COLLABORATION: "TEAM_COLLABORATION",
|
||||
SMS_NOTIFICATIONS: "SMS_NOTIFICATIONS",
|
||||
ADVANCED_REPORTS: "ADVANCED_REPORTS",
|
||||
} as const;
|
||||
|
||||
const ENTERPRISE_FEATURES = {
|
||||
...PREMIUM_FEATURES,
|
||||
UNLIMITED_STORAGE: "UNLIMITED_STORAGE",
|
||||
CUSTOM_BRANDING: "CUSTOM_BRANDING",
|
||||
DEDICATED_SUPPORT: "DEDICATED_SUPPORT",
|
||||
API_ACCESS: "API_ACCESS",
|
||||
USER_ROLES: "USER_ROLES",
|
||||
AUDIT_LOGS: "AUDIT_LOGS",
|
||||
SINGLE_SIGN_ON: "SINGLE_SIGN_ON",
|
||||
ADVANCED_ANALYTICS: "ADVANCED_ANALYTICS",
|
||||
} as const;
|
||||
|
||||
export const FEATURES = {
|
||||
[PricingPlanType.FREE]: FREE_FEATURES,
|
||||
[PricingPlanType.PREMIUM]: PREMIUM_FEATURES,
|
||||
[PricingPlanType.ENTERPRISE]: ENTERPRISE_FEATURES,
|
||||
} as const;
|
||||
|
||||
export type FreeFeature = (typeof FREE_FEATURES)[keyof typeof FREE_FEATURES];
|
||||
export type PremiumFeature =
|
||||
(typeof PREMIUM_FEATURES)[keyof typeof PREMIUM_FEATURES];
|
||||
export type EnterpriseFeature =
|
||||
(typeof ENTERPRISE_FEATURES)[keyof typeof ENTERPRISE_FEATURES];
|
||||
|
||||
export type Feature = FreeFeature | PremiumFeature | EnterpriseFeature;
|
||||
18
packages/billing/src/config/index.ts
Normal file
18
packages/billing/src/config/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { BillingStatus } from "../types";
|
||||
|
||||
import { discounts, plans } from "./plans";
|
||||
import { billingConfigSchema } from "./schema";
|
||||
|
||||
import type { BillingConfig } from "../types";
|
||||
|
||||
export const config = billingConfigSchema.parse({
|
||||
plans,
|
||||
discounts,
|
||||
}) satisfies BillingConfig;
|
||||
|
||||
export const ACTIVE_BILLING_STATUSES: BillingStatus[] = [
|
||||
BillingStatus.ACTIVE,
|
||||
BillingStatus.TRIALING,
|
||||
];
|
||||
|
||||
export * from "./features";
|
||||
107
packages/billing/src/config/plans.ts
Normal file
107
packages/billing/src/config/plans.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
BillingModel,
|
||||
PricingPlanType,
|
||||
RecurringInterval,
|
||||
BillingDiscountType,
|
||||
} from "../types";
|
||||
|
||||
import type { Discount } from "../types";
|
||||
|
||||
export const plans = [
|
||||
{
|
||||
id: PricingPlanType.FREE,
|
||||
name: "plan.starter.name",
|
||||
description: "plan.starter.description",
|
||||
badge: null,
|
||||
prices: [
|
||||
{
|
||||
id: "starter-lifetime",
|
||||
amount: 0,
|
||||
type: BillingModel.ONE_TIME,
|
||||
},
|
||||
{
|
||||
id: "starter-monthly",
|
||||
amount: 0,
|
||||
interval: RecurringInterval.MONTH,
|
||||
type: BillingModel.RECURRING,
|
||||
},
|
||||
{
|
||||
id: "starter-yearly",
|
||||
amount: 0,
|
||||
interval: RecurringInterval.YEAR,
|
||||
type: BillingModel.RECURRING,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: PricingPlanType.PREMIUM,
|
||||
name: "plan.premium.name",
|
||||
description: "plan.premium.description",
|
||||
badge: "plan.premium.badge",
|
||||
prices: [
|
||||
{
|
||||
id: "price_1PpUagFQH4McJDTlHCzOmyT6",
|
||||
amount: 29900,
|
||||
type: BillingModel.ONE_TIME,
|
||||
},
|
||||
{
|
||||
id: "price_1PpZAAFQH4McJDTlig6FBPyy",
|
||||
amount: 1900,
|
||||
interval: RecurringInterval.MONTH,
|
||||
trialDays: 7,
|
||||
type: BillingModel.RECURRING,
|
||||
},
|
||||
{
|
||||
id: "price_1PpZALFQH4McJDTl8SWorWTO",
|
||||
amount: 8900,
|
||||
interval: RecurringInterval.YEAR,
|
||||
trialDays: 7,
|
||||
type: BillingModel.RECURRING,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: PricingPlanType.ENTERPRISE,
|
||||
name: "plan.enterprise.name",
|
||||
description: "plan.enterprise.description",
|
||||
badge: null,
|
||||
prices: [
|
||||
{
|
||||
id: "enterprise-lifetime",
|
||||
label: "common:contactUs",
|
||||
href: "/contact",
|
||||
type: BillingModel.ONE_TIME,
|
||||
custom: true,
|
||||
},
|
||||
{
|
||||
id: "enterprise-monthly",
|
||||
label: "common:contactUs",
|
||||
href: "/contact",
|
||||
type: BillingModel.RECURRING,
|
||||
interval: RecurringInterval.MONTH,
|
||||
custom: true,
|
||||
},
|
||||
{
|
||||
id: "enterprise-yearly",
|
||||
label: "common:contactUs",
|
||||
href: "/contact",
|
||||
type: BillingModel.RECURRING,
|
||||
interval: RecurringInterval.YEAR,
|
||||
custom: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const discounts: Discount[] = [
|
||||
{
|
||||
code: "50OFF",
|
||||
type: BillingDiscountType.PERCENT,
|
||||
off: 50,
|
||||
appliesTo: [
|
||||
"price_1PpUagFQH4McJDTlHCzOmyT6",
|
||||
"price_1PpZAAFQH4McJDTlig6FBPyy",
|
||||
"price_1PpZALFQH4McJDTl8SWorWTO",
|
||||
],
|
||||
},
|
||||
];
|
||||
69
packages/billing/src/config/schema.ts
Normal file
69
packages/billing/src/config/schema.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as z from "zod";
|
||||
|
||||
import {
|
||||
BillingDiscountType,
|
||||
BillingModel,
|
||||
PricingPlanType,
|
||||
RecurringInterval,
|
||||
} from "../types";
|
||||
|
||||
export const discountSchema = z.object({
|
||||
code: z.string(),
|
||||
type: z.enum(BillingDiscountType),
|
||||
off: z.number(),
|
||||
appliesTo: z.array(z.string()),
|
||||
});
|
||||
|
||||
const customPriceSchema = z.union([
|
||||
z.object({
|
||||
custom: z.literal(true),
|
||||
label: z.string(),
|
||||
href: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
custom: z.literal(false).optional().default(false),
|
||||
amount: z.number(),
|
||||
}),
|
||||
]);
|
||||
|
||||
const sharedPriceSchema = z.intersection(
|
||||
customPriceSchema,
|
||||
z.object({
|
||||
id: z.string(),
|
||||
currency: z.string().optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const priceTypeSchema = z.discriminatedUnion("type", [
|
||||
z.object({
|
||||
type: z.literal(BillingModel.ONE_TIME),
|
||||
}),
|
||||
z.object({
|
||||
type: z.literal(BillingModel.RECURRING),
|
||||
interval: z.enum(RecurringInterval),
|
||||
trialDays: z.number().optional(),
|
||||
}),
|
||||
]);
|
||||
|
||||
export const priceSchema = z.intersection(sharedPriceSchema, priceTypeSchema);
|
||||
|
||||
export const planSchema = z.object({
|
||||
id: z.enum(PricingPlanType),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
badge: z.string().nullable().default(null),
|
||||
prices: z.array(priceSchema),
|
||||
});
|
||||
|
||||
export const billingConfigSchema = z.object({
|
||||
plans: z.array(planSchema).refine(
|
||||
(plans) => {
|
||||
const types = new Set(plans.map((plan) => plan.id));
|
||||
return types.size === plans.length;
|
||||
},
|
||||
{
|
||||
message: "You can't have two plans with the same id!",
|
||||
},
|
||||
),
|
||||
discounts: z.array(discountSchema).optional().default([]),
|
||||
});
|
||||
Reference in New Issue
Block a user