-
Notifications
You must be signed in to change notification settings - Fork 0
feat: workspace CRUD — control plane routes + dashboard UI #84
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -549,6 +549,91 @@ export async function getSystemInfo(): Promise<SystemInfo> { | |
| return res.json(); | ||
| } | ||
|
|
||
| // --- Workspaces --- | ||
|
|
||
| export interface WorkspaceRepo { | ||
| name: string; | ||
| role?: 'primary' | 'reference'; | ||
| branch?: string; | ||
| } | ||
|
|
||
| export interface Workspace { | ||
| id: string; | ||
| name: string; | ||
| description?: string; | ||
| type: 'monorepo' | 'multi-repo'; | ||
| repos: WorkspaceRepo[]; | ||
| rootDir?: string; | ||
| settings?: { | ||
| language?: string; | ||
| packageManager?: string; | ||
| testCommand?: string; | ||
| buildCommand?: string; | ||
| }; | ||
| daemonCount?: number; | ||
| createdAt: string; | ||
| updatedAt?: string; | ||
| } | ||
|
|
||
| export async function listWorkspaces(): Promise<{ workspaces: Workspace[] }> { | ||
| const res = await fetch('/v1/workspaces', { headers: apiKeyHeaders() }); | ||
| if (!res.ok) throw new Error(`Failed to fetch workspaces: ${res.status}`); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replace generic Line 580, Line 588, Line 602-605, Line 621-624, and Line 634 throw generic Proposed fix+type WorkspaceErrorCode =
+ | 'WORKSPACE_LIST_FAILED'
+ | 'WORKSPACE_GET_FAILED'
+ | 'WORKSPACE_CREATE_FAILED'
+ | 'WORKSPACE_UPDATE_FAILED'
+ | 'WORKSPACE_DELETE_FAILED';
+
+type WorkspaceApiError = Error & {
+ code: WorkspaceErrorCode;
+ status: number;
+};
+
+function createWorkspaceApiError(
+ code: WorkspaceErrorCode,
+ status: number,
+ message: string,
+): WorkspaceApiError {
+ return Object.assign(new Error(message), { code, status });
+}
...
- if (!res.ok) throw new Error(`Failed to fetch workspaces: ${res.status}`);
+ if (!res.ok) {
+ throw createWorkspaceApiError('WORKSPACE_LIST_FAILED', res.status, `Failed to fetch workspaces: ${res.status}`);
+ }As per coding guidelines, "Use typed errors with error codes (e.g., Also applies to: 588-588, 602-605, 621-624, 634-634 🤖 Prompt for AI Agents |
||
| return res.json(); | ||
| } | ||
|
Comment on lines
+578
to
+582
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate and examine the file
find . -type f -name "client.ts" | grep -E "apps/dashboard.*api"Repository: arek-e/paws Length of output: 89 🏁 Script executed: # Get file size to understand how much to read
wc -l apps/dashboard/src/api/client.tsRepository: arek-e/paws Length of output: 91 🏁 Script executed: # Read the relevant section of the file (around lines 578-627)
sed -n '570,635p' apps/dashboard/src/api/client.tsRepository: arek-e/paws Length of output: 2076 🏁 Script executed: # Check if Zod is already imported in the file
grep -n "zod\|Zod" apps/dashboard/src/api/client.ts | head -20Repository: arek-e/paws Length of output: 37 🏁 Script executed: # Check for the Workspace type definition
grep -n "type Workspace\|interface Workspace" apps/dashboard/src/api/client.tsRepository: arek-e/paws Length of output: 124 🏁 Script executed: # Get the full Workspace and WorkspaceRepo type definitions
sed -n '554,575p' apps/dashboard/src/api/client.tsRepository: arek-e/paws Length of output: 515 🏁 Script executed: # Check if Zod is used elsewhere in the dashboard project
find apps/dashboard -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "from 'zod'\|from \"zod\"" {} \;Repository: arek-e/paws Length of output: 37 🏁 Script executed: # Look at the imports at the top of the client.ts file to understand dependencies
head -30 apps/dashboard/src/api/client.tsRepository: arek-e/paws Length of output: 950 Add Zod validation for workspace API request/response payloads. The Proposed fix+import { z } from 'zod';
+
+const WorkspaceRepoSchema = z.object({
+ name: z.string(),
+ role: z.enum(['primary', 'reference']).optional(),
+ branch: z.string().optional(),
+});
+
+const WorkspaceSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ description: z.string().optional(),
+ type: z.enum(['monorepo', 'multi-repo']),
+ repos: z.array(WorkspaceRepoSchema),
+ rootDir: z.string().optional(),
+ settings: z
+ .object({
+ language: z.string().optional(),
+ packageManager: z.string().optional(),
+ testCommand: z.string().optional(),
+ buildCommand: z.string().optional(),
+ })
+ .optional(),
+ daemonCount: z.number().optional(),
+ createdAt: z.string(),
+ updatedAt: z.string().optional(),
+});
+
+const WorkspaceListSchema = z.object({
+ workspaces: z.array(WorkspaceSchema),
+});
+
export async function listWorkspaces(): Promise<{ workspaces: Workspace[] }> {
const res = await fetch('/v1/workspaces', { headers: apiKeyHeaders() });
if (!res.ok) throw new Error(`Failed to fetch workspaces: ${res.status}`);
- return res.json();
+ return WorkspaceListSchema.parse(await res.json());
}Per coding guidelines: "Use Zod for all external data validation: API requests, config files, and environment variables." 🤖 Prompt for AI Agents |
||
|
|
||
| export async function getWorkspace(id: string): Promise<Workspace> { | ||
| const res = await fetch(`/v1/workspaces/${encodeURIComponent(id)}`, { | ||
| headers: apiKeyHeaders(), | ||
| }); | ||
| if (!res.ok) throw new Error(`Failed to fetch workspace: ${res.status}`); | ||
| return res.json(); | ||
| } | ||
|
|
||
| export async function createWorkspace( | ||
| data: Omit<Workspace, 'id' | 'createdAt' | 'updatedAt' | 'daemonCount'>, | ||
| ): Promise<Workspace> { | ||
| const res = await fetch('/v1/workspaces', { | ||
| method: 'POST', | ||
| headers: apiKeyHeaders(), | ||
| body: JSON.stringify(data), | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.json().catch(() => ({})); | ||
| throw new Error( | ||
| (body as { error?: { message?: string } }).error?.message ?? | ||
| `Failed to create workspace: ${res.status}`, | ||
| ); | ||
| } | ||
| return res.json(); | ||
| } | ||
|
|
||
| export async function updateWorkspace( | ||
| id: string, | ||
| data: Partial<Omit<Workspace, 'id' | 'createdAt' | 'updatedAt' | 'daemonCount'>>, | ||
| ): Promise<Workspace> { | ||
| const res = await fetch(`/v1/workspaces/${encodeURIComponent(id)}`, { | ||
| method: 'PATCH', | ||
| headers: apiKeyHeaders(), | ||
| body: JSON.stringify(data), | ||
| }); | ||
| if (!res.ok) { | ||
| const body = await res.json().catch(() => ({})); | ||
| throw new Error( | ||
| (body as { error?: { message?: string } }).error?.message ?? | ||
| `Failed to update workspace: ${res.status}`, | ||
| ); | ||
| } | ||
| return res.json(); | ||
| } | ||
|
|
||
| export async function deleteWorkspace(id: string): Promise<void> { | ||
| const res = await fetch(`/v1/workspaces/${encodeURIComponent(id)}`, { | ||
| method: 'DELETE', | ||
| headers: apiKeyHeaders(), | ||
| }); | ||
| if (!res.ok) throw new Error(`Failed to delete workspace: ${res.status}`); | ||
| } | ||
|
|
||
| // --- Cloud Connections (AWS integration) --- | ||
|
|
||
| export interface CloudConnection { | ||
|
|
||
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.
Missing auth middleware for workspace routes.
The workspace CRUD endpoints lack auth middleware registration. Other
/v1/*domain routes (e.g., daemons, sessions, snapshots) have explicit auth middleware applied in lines 551-573. Without this, workspace routes are unprotected.🔒 Proposed fix to add auth middleware for workspace routes
Add these lines in the auth middleware registration section (around line 573):
app.use('/v1/mcp/*', authMiddleware(authConfig)); app.use('/v1/mcp', authMiddleware(authConfig)); app.use('/v1/settings', authMiddleware(authConfig)); app.use('/v1/settings/*', authMiddleware(authConfig)); +app.use('/v1/workspaces', authMiddleware(authConfig)); +app.use('/v1/workspaces/*', authMiddleware(authConfig));🤖 Prompt for AI Agents