Skip to content

Commit

Permalink
refactor: workspace limits (#1094)
Browse files Browse the repository at this point in the history
  • Loading branch information
mxkaske authored Nov 10, 2024
1 parent 4e4e0ac commit 0da6543
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 123 deletions.
8 changes: 2 additions & 6 deletions apps/web/src/config/pricing-table.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import type {
LimitsV1,
LimitsV2,
LimitsV3,
} from "@openstatus/db/src/schema/plan/schema";
import type { Limits } from "@openstatus/db/src/schema/plan/schema";
import Link from "next/link";
import type React from "react";

Expand Down Expand Up @@ -30,7 +26,7 @@ export const pricingTableConfig: Record<
{
label: string;
features: {
value: keyof LimitsV1 | keyof LimitsV2 | keyof LimitsV3;
value: keyof Limits;
label: string;
description?: React.ReactNode; // tooltip informations
badge?: string;
Expand Down
125 changes: 42 additions & 83 deletions packages/db/src/schema/plan/schema.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,49 @@
import { z } from "zod";
import { monitorFlyRegionSchema, monitorPeriodicitySchema } from "../constants";

// This is not a database table but just a schema for the limits of the plan
// REMINDER: this is not a database table but just a schema for the limits of the plan
// default values are set to the free plan limits

export const limitsV1 = z.object({
export const limitsSchema = z.object({
version: z.undefined(),
monitors: z.number(),
"synthetic-checks": z.number(),
periodicity: monitorPeriodicitySchema.array(),
"multi-region": z.boolean(),
"max-regions": z.number(),
"data-retention": z.enum(["14 days", "3 months", "12 months", "24 months"]),
// status pages
"status-pages": z.number(),
maintenance: z.boolean(),
"status-subscribers": z.boolean(),
"custom-domain": z.boolean(),
"password-protection": z.boolean(),
"white-label": z.boolean(),
// alerts
notifications: z.boolean(),
pagerduty: z.boolean(),
sms: z.boolean(),
"notification-channels": z.number(),
// collaboration
members: z.literal("Unlimited").or(z.number()),
"audit-log": z.boolean(),
regions: monitorFlyRegionSchema.array(),
/**
* Monitor limits
*/
monitors: z.number().default(1),
"synthetic-checks": z.number().default(30),
periodicity: monitorPeriodicitySchema.array().default(["10m", "30m", "1h"]),
"multi-region": z.boolean().default(true),
"max-regions": z.number().default(6),
"data-retention": z
.enum(["14 days", "3 months", "12 months", "24 months"])
.default("14 days"),
regions: monitorFlyRegionSchema
.array()
.default(["ams", "gru", "iad", "jnb", "hkg", "syd"]),
"private-locations": z.boolean().default(false),
screenshots: z.boolean().default(false),
/**
* Status page limits
*/
"status-pages": z.number().default(1),
maintenance: z.boolean().default(true),
"monitor-values-visibility": z.boolean().default(true),
"status-subscribers": z.boolean().default(false),
"custom-domain": z.boolean().default(false),
"password-protection": z.boolean().default(false),
"white-label": z.boolean().default(false),
/**
* Notification limits
*/
notifications: z.boolean().default(true),
pagerduty: z.boolean().default(false),
sms: z.boolean().default(false),
"notification-channels": z.number().default(1),
/**
* Collaboration limits
*/
members: z.literal("Unlimited").or(z.number()).default(1),
"audit-log": z.boolean().default(false),
});

export type LimitsV1 = z.infer<typeof limitsV1>;
export const limitsV2 = limitsV1.extend({
version: z.literal("v2"),
"private-locations": z.boolean(),
"monitor-values-visibility": z.boolean(),
});

export const limitsV3 = limitsV2.extend({
version: z.literal("v3"),
screenshots: z.boolean(),
});

export type LimitsV2 = z.infer<typeof limitsV2>;
export type LimitsV3 = z.infer<typeof limitsV3>;

const unknownLimit = z.discriminatedUnion("version", [
limitsV1,
limitsV2,
limitsV3,
]);

export function migrateFromV1ToV2({ data }: { data: LimitsV1 }) {
return {
version: "v2",
...data,
"private-locations": true,
"monitor-values-visibility": true,
};
}

export function migrateFromV2ToV3({ data }: { data: LimitsV2 }) {
return {
...data,
version: "v3",
screenshots: true,
};
}

export function migrateFromV1ToV3({ data }: { data: LimitsV1 }) {
return {
...data,
version: "v3",
screenshots: true,
"private-locations": true,
"monitor-values-visibility": true,
};
}

export const limitSchema = unknownLimit.transform((val) => {
if (!val.version) {
return migrateFromV1ToV3({ data: val });
}
if (val.version === "v2") {
return migrateFromV2ToV3({ data: val });
}
return val;
});

export type Limits = z.infer<typeof unknownLimit>;
export type Limits = z.infer<typeof limitsSchema>;
45 changes: 11 additions & 34 deletions packages/db/src/schema/workspaces/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createSelectSchema } from "drizzle-zod";
import { z } from "zod";

import { allPlans } from "../plan/config";
import { limitsV1 } from "../plan/schema";
import { limitsSchema } from "../plan/schema";
import { workspacePlans, workspaceRole } from "./constants";
import { workspace } from "./workspace";

Expand All @@ -11,13 +11,12 @@ export const workspaceRoleSchema = z.enum(workspaceRole);

/**
* Workspace schema with limits and plan
* If not available in the db, the limits will be taken from the workspace plan
*/
const selectWorkspaceSchemaDevelopment = createSelectSchema(workspace)
export const selectWorkspaceSchema = createSelectSchema(workspace)
.extend({
limits: z.string().transform((val) => {
const parsed = JSON.parse(val);
const result = limitsV1.partial().safeParse(parsed);
const result = limitsSchema.partial().safeParse(parsed);
if (result.error) return {};
return result.data;
}),
Expand All @@ -30,39 +29,17 @@ const selectWorkspaceSchemaDevelopment = createSelectSchema(workspace)
.transform((val) => {
return {
...val,
limits: limitsV1.parse({ ...allPlans[val.plan].limits, ...val.limits }),
limits: limitsSchema.parse({
...allPlans[val.plan].limits,
/**
* override the default plan limits
* allows us to set custom limits for a workspace
*/
...val.limits,
}),
};
});

/**
* Workspace schema with limits and plan
* The limits for paid users have to be defined within the db otherwise, fallbacks to free plan limits
*/
const selectWorkspaceSchemaProduction = createSelectSchema(workspace).extend({
limits: z.string().transform((val) => {
const parsed = JSON.parse(val);
const result = limitsV1.safeParse(parsed);
if (result.error) {
// Fallback to default limits
return limitsV1.parse({
...allPlans.free.limits,
});
}

return result.data;
}),
plan: z
.enum(workspacePlans)
.nullable()
.default("free")
.transform((val) => val ?? "free"),
});

export const selectWorkspaceSchema =
process.env.NODE_ENV === "development"
? selectWorkspaceSchemaDevelopment
: selectWorkspaceSchemaProduction;

export const insertWorkspaceSchema = createSelectSchema(workspace);

export type Workspace = z.infer<typeof selectWorkspaceSchema>;
Expand Down

0 comments on commit 0da6543

Please sign in to comment.