-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add POST /api/workspaces endpoint for workspace creation #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: test
Are you sure you want to change the base?
Conversation
- Create insertAccountWorkspaceId helper for owner-workspace linking - Create createWorkspaceInDb with account, info, owner link, and org link - Add validateCreateWorkspaceBody with API key and Bearer token auth - Add organization access validation for workspace linking - Expose POST /api/workspaces route with CORS support
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| if (organizationId) { | ||
| await addArtistToOrganization(account.id, organizationId); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Organization linking failures are silently ignored, causing the API to return success (201) even when the workspace fails to link to the requested organization.
View Details
📝 Patch Details
diff --git a/lib/artists/__tests__/createArtistInDb.test.ts b/lib/artists/__tests__/createArtistInDb.test.ts
index e979fbb..9a02b15 100644
--- a/lib/artists/__tests__/createArtistInDb.test.ts
+++ b/lib/artists/__tests__/createArtistInDb.test.ts
@@ -133,4 +133,17 @@ describe("createArtistInDb", () => {
expect(result).toBeNull();
});
+
+ it("returns null when linking artist to organization fails", async () => {
+ mockInsertAccount.mockResolvedValue(mockAccount);
+ mockInsertAccountInfo.mockResolvedValue(mockAccountInfo);
+ mockSelectAccountWithSocials.mockResolvedValue(mockFullAccount);
+ mockInsertAccountArtistId.mockResolvedValue({ id: "rel-123" });
+ mockAddArtistToOrganization.mockResolvedValue(null);
+
+ const result = await createArtistInDb("Test Artist", "owner-456", "org-789");
+
+ expect(mockAddArtistToOrganization).toHaveBeenCalledWith("artist-123", "org-789");
+ expect(result).toBeNull();
+ });
});
diff --git a/lib/artists/createArtistInDb.ts b/lib/artists/createArtistInDb.ts
index e1eeceb..3990e08 100644
--- a/lib/artists/createArtistInDb.ts
+++ b/lib/artists/createArtistInDb.ts
@@ -44,7 +44,8 @@ export async function createArtistInDb(
// Step 5: Link to organization if provided
if (organizationId) {
- await addArtistToOrganization(account.id, organizationId);
+ const organizationLinkId = await addArtistToOrganization(account.id, organizationId);
+ if (!organizationLinkId) return null;
}
return {
diff --git a/lib/workspaces/createWorkspaceInDb.ts b/lib/workspaces/createWorkspaceInDb.ts
index d7684c5..8a81851 100644
--- a/lib/workspaces/createWorkspaceInDb.ts
+++ b/lib/workspaces/createWorkspaceInDb.ts
@@ -41,7 +41,8 @@ export async function createWorkspaceInDb(
if (!linkId) return null;
if (organizationId) {
- await addArtistToOrganization(account.id, organizationId);
+ const organizationLinkId = await addArtistToOrganization(account.id, organizationId);
+ if (!organizationLinkId) return null;
}
return {
Analysis
Organization linking failures silently ignored in workspace and artist creation
What fails: createWorkspaceInDb() and createArtistInDb() return the created workspace/artist object and allow the API to return 201 (success) even when addArtistToOrganization() fails to link the workspace/artist to the requested organization.
How to reproduce:
- Create a workspace with an organizationId parameter:
POST /api/workspaceswith body{"name":"Test","organization_id":"org-123"} - The Supabase upsert in
addArtistToOrganization()fails (database constraint violation, timeout, permission error, etc.) - The function returns
nullto indicate failure - The calling code ignores this return value and continues execution
- The handler receives the workspace object and returns 201 Created with the workspace data
Result: API returns HTTP 201 with the workspace object, indicating success. However, the workspace is NOT linked to the requested organization.
Expected: When organization linking is explicitly requested and fails, the entire workspace/artist creation should fail. The API should return a 500 error and the workspace should not be created (or should be rolled back). This is consistent with how other critical operations are handled:
- If
insertAccountInfo()returns null → function returns null → API returns 500 - If
insertAccountWorkspaceId()returns null → function returns null → API returns 500 - If
addArtistToOrganization()returns null → function should return null → API should return 500
The fix validates the return value from addArtistToOrganization() and propagates the failure, consistent with the error-handling pattern established in the handler code (addArtistToOrgHandler.ts checks the return value and returns 500 if it fails).
Files fixed:
lib/workspaces/createWorkspaceInDb.ts(lines 43-45)lib/artists/createArtistInDb.ts(lines 43-47)lib/artists/__tests__/createArtistInDb.test.ts(added test case for organization linking failure)
| try { | ||
| const account = await insertAccount({ name }); | ||
|
|
||
| const accountInfo = await insertAccountInfo({ account_id: account.id }); | ||
| if (!accountInfo) return null; | ||
|
|
||
| const workspace = await selectAccountWithSocials(account.id); | ||
| if (!workspace) return null; | ||
|
|
||
| const linkId = await insertAccountWorkspaceId(accountId, account.id); | ||
| if (!linkId) return null; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If insertAccountInfo fails, an orphaned account record is left in the database while the API returns a 500 error to the user.
View Details
📝 Patch Details
diff --git a/lib/supabase/accounts/deleteAccount.ts b/lib/supabase/accounts/deleteAccount.ts
new file mode 100644
index 0000000..28379eb
--- /dev/null
+++ b/lib/supabase/accounts/deleteAccount.ts
@@ -0,0 +1,18 @@
+import supabase from "../serverClient";
+
+/**
+ * Deletes an account by its ID
+ *
+ * @param accountId - The ID of the account to delete
+ * @returns true if the delete was successful, false otherwise
+ */
+export async function deleteAccount(accountId: string): Promise<boolean> {
+ const { error } = await supabase.from("accounts").delete().eq("id", accountId);
+
+ if (error) {
+ console.error("[ERROR] deleteAccount:", error);
+ return false;
+ }
+
+ return true;
+}
diff --git a/lib/workspaces/createWorkspaceInDb.ts b/lib/workspaces/createWorkspaceInDb.ts
index d7684c5..21b98e9 100644
--- a/lib/workspaces/createWorkspaceInDb.ts
+++ b/lib/workspaces/createWorkspaceInDb.ts
@@ -1,4 +1,5 @@
import { insertAccount } from "@/lib/supabase/accounts/insertAccount";
+import { deleteAccount } from "@/lib/supabase/accounts/deleteAccount";
import { insertAccountInfo } from "@/lib/supabase/account_info/insertAccountInfo";
import {
selectAccountWithSocials,
@@ -32,13 +33,25 @@ export async function createWorkspaceInDb(
const account = await insertAccount({ name });
const accountInfo = await insertAccountInfo({ account_id: account.id });
- if (!accountInfo) return null;
+ if (!accountInfo) {
+ // Clean up the orphaned account record if account_info creation fails
+ await deleteAccount(account.id);
+ return null;
+ }
const workspace = await selectAccountWithSocials(account.id);
- if (!workspace) return null;
+ if (!workspace) {
+ // Clean up if we can't retrieve the workspace
+ await deleteAccount(account.id);
+ return null;
+ }
const linkId = await insertAccountWorkspaceId(accountId, account.id);
- if (!linkId) return null;
+ if (!linkId) {
+ // Clean up if workspace link creation fails
+ await deleteAccount(account.id);
+ return null;
+ }
if (organizationId) {
await addArtistToOrganization(account.id, organizationId);
Analysis
Orphaned account records left in database when workspace creation partially fails
What fails: createWorkspaceInDb() can leave an orphaned account record in the database if any operation after insertAccount() fails, while the API returns a 500 error to the user. The account is created and committed to the database, but subsequent operations (particularly insertAccountInfo()) may fail due to database errors, network issues, or permission problems. When these operations fail, the account record remains in the database despite the overall workspace creation being reported as failed.
How to reproduce:
// Call createWorkspaceInDb with conditions that cause insertAccountInfo to fail
// Example: Database temporarily unavailable after account insertion
// This will insert the account but then return null when insertAccountInfo fails
const workspace = await createWorkspaceInDb("Test Workspace", "owner-account-id");
// Result: Returns null (500 error to user)
// Side effect: Account record exists in database with no corresponding account_info recordResult: Orphaned account record exists in the database. The account was successfully inserted but account_info and subsequent operations failed, leaving partial state in the database.
Expected: Either all operations succeed and a complete workspace is created, or none persist any state. The operation should be atomic from the user's perspective.
Fix implemented: Added cleanup logic to delete the orphaned account record when any subsequent operation fails. Created deleteAccount() function and updated createWorkspaceInDb() to call it on failure paths, ensuring no orphaned records are left in the database.
Technical background: Supabase does not support client-side transactions directly (unlike traditional ORMs). Operations are executed sequentially, so partial failures can occur. The fix ensures data consistency by cleaning up partial state before returning null to the caller.
Summary
Adds a new API endpoint for creating workspaces with proper authentication and organization linking.
Changes
POST /api/workspaces- Create workspace with Bearer token or API key authorganization_idparameter to link workspace to an orgFiles Added
app/api/workspaces/route.ts- API routelib/workspaces/createWorkspaceInDb.ts- Database creation logiclib/workspaces/createWorkspacePostHandler.ts- Request handlerlib/workspaces/validateCreateWorkspaceBody.ts- Auth + validationlib/supabase/account_workspace_ids/insertAccountWorkspaceId.ts- Owner link helper