diff --git a/apps/api/src/routes/v1_keys_getVerifications.security.test.ts b/apps/api/src/routes/v1_keys_getVerifications.security.test.ts index 9b9e4c4aab..0a326eb9ed 100644 --- a/apps/api/src/routes/v1_keys_getVerifications.security.test.ts +++ b/apps/api/src/routes/v1_keys_getVerifications.security.test.ts @@ -75,10 +75,12 @@ test("cannot read keys from a different workspace", async (t) => { const h = await IntegrationHarness.init(t); const workspaceId = newId("workspace"); + const name = randomUUID(); await h.db.primary.insert(schema.workspaces).values({ id: workspaceId, orgId: randomUUID(), - name: randomUUID(), + name: name, + slug: name, features: {}, betaFeatures: {}, }); diff --git a/apps/dashboard/app/(app)/gateway-new/page.tsx b/apps/dashboard/app/(app)/gateway-new/page.tsx index fc76b1033f..852d85cb08 100644 --- a/apps/dashboard/app/(app)/gateway-new/page.tsx +++ b/apps/dashboard/app/(app)/gateway-new/page.tsx @@ -8,6 +8,7 @@ * 4. The user is redirected to create their API */ +import { randomInt } from "node:crypto"; import { getAuth } from "@/lib/auth"; import { db, schema } from "@/lib/db"; import { freeTierQuotas } from "@/lib/quotas"; @@ -28,6 +29,7 @@ export default async function Page() { await db.insert(schema.workspaces).values({ id, name: "Personal Workspace", + slug: `personal-workspace-${randomInt(100000)}`, orgId, betaFeatures: {}, features: {}, diff --git a/deployment/04-seed-workspace.sql b/deployment/04-seed-workspace.sql index 209a661712..13b6d64f82 100644 --- a/deployment/04-seed-workspace.sql +++ b/deployment/04-seed-workspace.sql @@ -8,6 +8,7 @@ INSERT INTO workspaces ( id, org_id, name, + slug, created_at_m, beta_features, features @@ -15,6 +16,7 @@ INSERT INTO workspaces ( 'ws_local_root', 'user_REPLACE_ME', 'Unkey', + 'unkey', UNIX_TIMESTAMP() * 1000, '{}', '{}' diff --git a/go/apps/api/routes/v2_identities_list_identities/200_test.go b/go/apps/api/routes/v2_identities_list_identities/200_test.go index 61991cae7c..3eb8415c3a 100644 --- a/go/apps/api/routes/v2_identities_list_identities/200_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/200_test.go @@ -292,6 +292,7 @@ func TestSuccess(t *testing.T) { err = db.Query.InsertWorkspace(ctx, tx, db.InsertWorkspaceParams{ ID: singleWorkspaceID, Name: "Single Identity Workspace", + Slug: uid.New("slug"), OrgID: uid.New(uid.OrgPrefix), CreatedAt: time.Now().UnixMilli(), }) diff --git a/go/apps/api/routes/v2_identities_list_identities/400_test.go b/go/apps/api/routes/v2_identities_list_identities/400_test.go index 4c2d7f7ee3..a25126650c 100644 --- a/go/apps/api/routes/v2_identities_list_identities/400_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/400_test.go @@ -2,6 +2,7 @@ package handler_test import ( "fmt" + "maps" "net/http" "strings" "testing" @@ -80,9 +81,7 @@ func TestBadRequests(t *testing.T) { t.Run("malformed JSON body", func(t *testing.T) { customHeaders := make(http.Header) - for k, v := range headers { - customHeaders[k] = v - } + maps.Copy(customHeaders, headers) customHeaders.Set("Content-Type", "application/json") // Create a malformed JSON string diff --git a/go/apps/api/routes/v2_identities_list_identities/cross_workspace_test.go b/go/apps/api/routes/v2_identities_list_identities/cross_workspace_test.go index 93df44c665..511c343b6d 100644 --- a/go/apps/api/routes/v2_identities_list_identities/cross_workspace_test.go +++ b/go/apps/api/routes/v2_identities_list_identities/cross_workspace_test.go @@ -46,6 +46,7 @@ func TestCrossWorkspaceForbidden(t *testing.T) { err = db.Query.InsertWorkspace(ctx, tx, db.InsertWorkspaceParams{ ID: workspaceB, Name: "Test Workspace B", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), OrgID: uid.New("org"), }) diff --git a/go/apps/api/routes/v2_keys_add_permissions/404_test.go b/go/apps/api/routes/v2_keys_add_permissions/404_test.go index ad7296b786..87368ea5e5 100644 --- a/go/apps/api/routes/v2_keys_add_permissions/404_test.go +++ b/go/apps/api/routes/v2_keys_add_permissions/404_test.go @@ -81,6 +81,7 @@ func TestNotFoundErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: "Other Workspace", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) diff --git a/go/apps/api/routes/v2_keys_remove_permissions/403_test.go b/go/apps/api/routes/v2_keys_remove_permissions/403_test.go index 214a5f8f30..0f6fe33b4d 100644 --- a/go/apps/api/routes/v2_keys_remove_permissions/403_test.go +++ b/go/apps/api/routes/v2_keys_remove_permissions/403_test.go @@ -114,6 +114,7 @@ func TestAuthorizationErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: "Other Workspace", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) diff --git a/go/apps/api/routes/v2_keys_remove_permissions/404_test.go b/go/apps/api/routes/v2_keys_remove_permissions/404_test.go index 32a4d5b3e5..546b38d1f8 100644 --- a/go/apps/api/routes/v2_keys_remove_permissions/404_test.go +++ b/go/apps/api/routes/v2_keys_remove_permissions/404_test.go @@ -118,6 +118,7 @@ func TestNotFoundErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: "Other Workspace", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -189,6 +190,7 @@ func TestNotFoundErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: "Other Workspace", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) diff --git a/go/apps/api/routes/v2_keys_set_roles/404_test.go b/go/apps/api/routes/v2_keys_set_roles/404_test.go index 3a8d6536ba..63963f483e 100644 --- a/go/apps/api/routes/v2_keys_set_roles/404_test.go +++ b/go/apps/api/routes/v2_keys_set_roles/404_test.go @@ -152,6 +152,7 @@ func TestNotFoundErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: uid.New("test_name"), + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) @@ -215,6 +216,7 @@ func TestNotFoundErrors(t *testing.T) { ID: otherWorkspaceID, OrgID: uid.New("test_org"), Name: uid.New("test_name"), + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) diff --git a/go/pkg/db/bulk_workspace_insert.sql.go b/go/pkg/db/bulk_workspace_insert.sql.go index 5a406fcd48..2aa27b0eaa 100644 --- a/go/pkg/db/bulk_workspace_insert.sql.go +++ b/go/pkg/db/bulk_workspace_insert.sql.go @@ -9,7 +9,7 @@ import ( ) // bulkInsertWorkspace is the base query for bulk insert -const bulkInsertWorkspace = `INSERT INTO ` + "`" + `workspaces` + "`" + ` ( id, org_id, name, created_at_m, tier, beta_features, features, enabled, delete_protection ) VALUES %s` +const bulkInsertWorkspace = `INSERT INTO ` + "`" + `workspaces` + "`" + ` ( id, org_id, name, slug, created_at_m, tier, beta_features, features, enabled, delete_protection ) VALUES %s` // InsertWorkspaces performs bulk insert in a single query func (q *BulkQueries) InsertWorkspaces(ctx context.Context, db DBTX, args []InsertWorkspaceParams) error { @@ -21,7 +21,7 @@ func (q *BulkQueries) InsertWorkspaces(ctx context.Context, db DBTX, args []Inse // Build the bulk insert query valueClauses := make([]string, len(args)) for i := range args { - valueClauses[i] = "( ?, ?, ?, ?, 'Free', '{}', '{}', true, true )" + valueClauses[i] = "( ?, ?, ?, ?, ?, 'Free', '{}', '{}', true, true )" } bulkQuery := fmt.Sprintf(bulkInsertWorkspace, strings.Join(valueClauses, ", ")) @@ -32,6 +32,7 @@ func (q *BulkQueries) InsertWorkspaces(ctx context.Context, db DBTX, args []Inse allArgs = append(allArgs, arg.ID) allArgs = append(allArgs, arg.OrgID) allArgs = append(allArgs, arg.Name) + allArgs = append(allArgs, arg.Slug) allArgs = append(allArgs, arg.CreatedAt) } diff --git a/go/pkg/db/models_generated.go b/go/pkg/db/models_generated.go index 33a63a0aba..c4f20fc5ce 100644 --- a/go/pkg/db/models_generated.go +++ b/go/pkg/db/models_generated.go @@ -771,7 +771,7 @@ type Workspace struct { ID string `db:"id"` OrgID string `db:"org_id"` Name string `db:"name"` - Slug sql.NullString `db:"slug"` + Slug string `db:"slug"` PartitionID sql.NullString `db:"partition_id"` Plan NullWorkspacesPlan `db:"plan"` Tier sql.NullString `db:"tier"` diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index 9bbb17386e..8abfe5fccb 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -1187,6 +1187,7 @@ type Querier interface { // id, // org_id, // name, + // slug, // created_at_m, // tier, // beta_features, @@ -1198,7 +1199,8 @@ type Querier interface { // ?, // ?, // ?, - // ?, + // ?, + // ?, // 'Free', // '{}', // '{}', diff --git a/go/pkg/db/queries/workspace_insert.sql b/go/pkg/db/queries/workspace_insert.sql index 4b7083176d..76e8f69e78 100644 --- a/go/pkg/db/queries/workspace_insert.sql +++ b/go/pkg/db/queries/workspace_insert.sql @@ -3,6 +3,7 @@ INSERT INTO `workspaces` ( id, org_id, name, + slug, created_at_m, tier, beta_features, @@ -14,7 +15,8 @@ VALUES ( sqlc.arg(id), sqlc.arg(org_id), sqlc.arg(name), - sqlc.arg(created_at), + sqlc.arg(slug), + sqlc.arg(created_at), 'Free', '{}', '{}', diff --git a/go/pkg/db/retry_test.go b/go/pkg/db/retry_test.go index c4d8463f43..014fb46aac 100644 --- a/go/pkg/db/retry_test.go +++ b/go/pkg/db/retry_test.go @@ -144,6 +144,7 @@ func TestWithRetry_Integration(t *testing.T) { ID: workspaceID, OrgID: workspaceID, Name: "Test Workspace", + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), }) require.NoError(t, err) diff --git a/go/pkg/db/schema.sql b/go/pkg/db/schema.sql index 11d3c6c07d..9a67348e57 100644 --- a/go/pkg/db/schema.sql +++ b/go/pkg/db/schema.sql @@ -182,7 +182,7 @@ CREATE TABLE `workspaces` ( `id` varchar(256) NOT NULL, `org_id` varchar(256) NOT NULL, `name` varchar(256) NOT NULL, - `slug` varchar(64), + `slug` varchar(64) NOT NULL, `partition_id` varchar(256), `plan` enum('free','pro','enterprise') DEFAULT 'free', `tier` varchar(256) DEFAULT 'Free', diff --git a/go/pkg/db/workspace_insert.sql_generated.go b/go/pkg/db/workspace_insert.sql_generated.go index 7b6c36ffaa..2df02e9a82 100644 --- a/go/pkg/db/workspace_insert.sql_generated.go +++ b/go/pkg/db/workspace_insert.sql_generated.go @@ -14,6 +14,7 @@ INSERT INTO ` + "`" + `workspaces` + "`" + ` ( id, org_id, name, + slug, created_at_m, tier, beta_features, @@ -25,7 +26,8 @@ VALUES ( ?, ?, ?, - ?, + ?, + ?, 'Free', '{}', '{}', @@ -38,6 +40,7 @@ type InsertWorkspaceParams struct { ID string `db:"id"` OrgID string `db:"org_id"` Name string `db:"name"` + Slug string `db:"slug"` CreatedAt int64 `db:"created_at"` } @@ -47,6 +50,7 @@ type InsertWorkspaceParams struct { // id, // org_id, // name, +// slug, // created_at_m, // tier, // beta_features, @@ -58,7 +62,8 @@ type InsertWorkspaceParams struct { // ?, // ?, // ?, -// ?, +// ?, +// ?, // 'Free', // '{}', // '{}', @@ -70,6 +75,7 @@ func (q *Queries) InsertWorkspace(ctx context.Context, db DBTX, arg InsertWorksp arg.ID, arg.OrgID, arg.Name, + arg.Slug, arg.CreatedAt, ) return err diff --git a/go/pkg/testutil/seed/seed.go b/go/pkg/testutil/seed/seed.go index 9d27cfa27c..cd14ec2724 100644 --- a/go/pkg/testutil/seed/seed.go +++ b/go/pkg/testutil/seed/seed.go @@ -50,6 +50,7 @@ func (s *Seeder) CreateWorkspace(ctx context.Context) db.Workspace { ID: uid.New("test_ws"), OrgID: uid.New("test_org"), Name: uid.New("test_name"), + Slug: uid.New("slug"), CreatedAt: time.Now().UnixMilli(), } diff --git a/internal/db/src/schema/workspaces.ts b/internal/db/src/schema/workspaces.ts index 1fda7ac840..5f0e033018 100644 --- a/internal/db/src/schema/workspaces.ts +++ b/internal/db/src/schema/workspaces.ts @@ -20,7 +20,7 @@ export const workspaces = mysqlTable("workspaces", { name: varchar("name", { length: 256 }).notNull(), // slug is used for the workspace URL - slug: varchar("slug", { length: 64 }).unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(), // Deployment platform - which partition this workspace deploys to partitionId: varchar("partition_id", { length: 256 }), diff --git a/internal/resend/emails/api_v1_migration.tsx b/internal/resend/emails/api_v1_migration.tsx index 8c2759bf80..1f677c7db1 100644 --- a/internal/resend/emails/api_v1_migration.tsx +++ b/internal/resend/emails/api_v1_migration.tsx @@ -16,7 +16,11 @@ export type Props = { deprecatedEndpoints: string[]; }; -export function ApiV1Migration({ username, workspaceName, deprecatedEndpoints }: Props) { +export function ApiV1Migration({ + username = "User", + workspaceName = "Your Workspace", + deprecatedEndpoints = [], +}: Props) { return (