feat: set workspace url when creating a new workspace (#172)

* feat: add migration to remove not null constraint from referenceId on subscriptions

* feat(cloud): write worspace slug availability checks to db

* feat: add migration to add cascade set null to referenceId on subscription

* feat: update workspace and subscripton schema

* feat: add createWorkspaceSlugCheck repo func

* feat: set workspace url on creation

* chore: update translations

* refactor: various tweaks and fixes

* feat: add pro bolt to pricing notice

* feat: toggle pro subscription

* fix: hide pro subscription notice for self hosters

* feat: add pro upgrade message on members page

* feat: hide pro upgrade notice on small viewports

* feat: update workspace url placeholder
This commit is contained in:
Henry
2025-09-11 20:32:02 +01:00
committed by GitHub
parent 4f7fa1a228
commit 2f88366418
31 changed files with 8942 additions and 385 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE "subscription" ALTER COLUMN "referenceId" DROP NOT NULL;

View File

@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS "workspace_slug_checks" (
"id" bigserial PRIMARY KEY NOT NULL,
"slug" varchar(255) NOT NULL,
"available" boolean NOT NULL,
"reserved" boolean NOT NULL,
"workspaceId" bigint,
"createdBy" uuid,
"createdAt" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "workspace_slug_checks" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "workspace_slug_checks" ADD CONSTRAINT "workspace_slug_checks_workspaceId_workspace_id_fk" FOREIGN KEY ("workspaceId") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "workspace_slug_checks" ADD CONSTRAINT "workspace_slug_checks_createdBy_user_id_fk" FOREIGN KEY ("createdBy") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1,7 @@
ALTER TABLE "subscription" DROP CONSTRAINT "subscription_referenceId_workspace_publicId_fk";
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_referenceId_workspace_publicId_fk" FOREIGN KEY ("referenceId") REFERENCES "public"."workspace"("publicId") ON DELETE set null ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -78,6 +78,27 @@
"when": 1757271312974,
"tag": "20250907185512_AddUnlimitedSeatsToSubscription",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1757533858687,
"tag": "20250910195058_RemoveNotNullConstraintFromReferenceIdOnSubsriptions",
"breakpoints": true
},
{
"idx": 12,
"version": "7",
"when": 1757534656098,
"tag": "20250910200416_AddWorkspaceSlugChecks",
"breakpoints": true
},
{
"idx": 13,
"version": "7",
"when": 1757535838766,
"tag": "20250910202358_AddCascadeSetNullToReferenceIdOnSubsriptions",
"breakpoints": true
}
]
}

View File

@@ -1,7 +1,7 @@
import { eq } from "drizzle-orm";
import type { dbClient } from "@kan/db/client";
import { slugs } from "@kan/db/schema";
import { slugChecks, slugs } from "@kan/db/schema";
export const getWorkspaceSlug = (db: dbClient, slug: string) => {
return db.query.slugs.findFirst({
@@ -12,3 +12,20 @@ export const getWorkspaceSlug = (db: dbClient, slug: string) => {
where: eq(slugs.slug, slug),
});
};
export const createWorkspaceSlugCheck = (
db: dbClient,
input: {
slug: string;
userId: string;
available: boolean;
reserved: boolean;
},
) => {
return db.insert(slugChecks).values({
slug: input.slug,
available: input.available,
reserved: input.reserved,
createdBy: input.userId,
});
};

View File

@@ -13,9 +13,10 @@ import { workspaces } from "./workspaces";
export const subscription = pgTable("subscription", {
id: bigserial("id", { mode: "number" }).primaryKey(),
plan: varchar("plan", { length: 255 }).notNull(),
referenceId: varchar("referenceId", { length: 12 })
.notNull()
.references(() => workspaces.publicId),
referenceId: varchar("referenceId", { length: 12 }).references(
() => workspaces.publicId,
{ onDelete: "set null" },
),
stripeCustomerId: varchar("stripeCustomerId", { length: 255 }),
stripeSubscriptionId: varchar("stripeSubscriptionId", { length: 255 }),
status: varchar("status", { length: 255 }).notNull(),

View File

@@ -2,6 +2,7 @@ import { relations } from "drizzle-orm";
import {
bigint,
bigserial,
boolean,
pgEnum,
pgTable,
text,
@@ -103,3 +104,17 @@ export const slugs = pgTable("workspace_slugs", {
slug: varchar("slug", { length: 255 }).notNull().unique(),
type: slugTypeEnum("type").notNull(),
});
export const slugChecks = pgTable("workspace_slug_checks", {
id: bigserial("id", { mode: "number" }).primaryKey(),
slug: varchar("slug", { length: 255 }).notNull(),
available: boolean("available").notNull(),
reserved: boolean("reserved").notNull(),
workspaceId: bigint("workspaceId", { mode: "number" }).references(
() => workspaces.id,
),
createdBy: uuid("createdBy").references(() => users.id, {
onDelete: "set null",
}),
createdAt: timestamp("createdAt").defaultNow().notNull(),
}).enableRLS();