You've already forked agentic-coding-workflow
feat(workspace): migration 0003 + errs codes for workspace/worktree/git
This commit is contained in:
@@ -32,6 +32,18 @@ const (
|
|||||||
CodeProjectSlugTaken Code = "project_slug_taken"
|
CodeProjectSlugTaken Code = "project_slug_taken"
|
||||||
CodeProjectArchived Code = "project_archived"
|
CodeProjectArchived Code = "project_archived"
|
||||||
CodeRequirementClosed Code = "requirement_closed"
|
CodeRequirementClosed Code = "requirement_closed"
|
||||||
|
|
||||||
|
// Workspace 模块细粒度 code
|
||||||
|
CodeWorkspaceSlugTaken Code = "workspace_slug_taken"
|
||||||
|
CodeWorkspaceCloneFailed Code = "workspace_clone_failed"
|
||||||
|
CodeWorkspaceSyncInProgress Code = "workspace_sync_in_progress"
|
||||||
|
CodeWorkspaceRemoteInvalid Code = "workspace_remote_invalid"
|
||||||
|
CodeWorktreeBranchConflict Code = "worktree_branch_conflict"
|
||||||
|
CodeWorktreeAlreadyActive Code = "worktree_already_active"
|
||||||
|
CodeWorktreeNotHeldByCaller Code = "worktree_not_held_by_caller"
|
||||||
|
CodeWorktreePathInvalid Code = "worktree_path_invalid"
|
||||||
|
CodeGitCredentialInvalid Code = "git_credential_invalid"
|
||||||
|
CodeGitCmdFailed Code = "git_cmd_failed"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppError is the application's structured error type. It carries a stable
|
// AppError is the application's structured error type. It carries a stable
|
||||||
@@ -103,7 +115,9 @@ func HTTPStatus(code Code) int {
|
|||||||
case CodeNotFound:
|
case CodeNotFound:
|
||||||
return http.StatusNotFound
|
return http.StatusNotFound
|
||||||
case CodeConflict,
|
case CodeConflict,
|
||||||
CodeProjectSlugTaken, CodeProjectArchived, CodeRequirementClosed:
|
CodeProjectSlugTaken, CodeProjectArchived, CodeRequirementClosed,
|
||||||
|
CodeWorkspaceSlugTaken, CodeWorkspaceSyncInProgress,
|
||||||
|
CodeWorktreeBranchConflict, CodeWorktreeAlreadyActive:
|
||||||
return http.StatusConflict
|
return http.StatusConflict
|
||||||
case CodeRateLimited:
|
case CodeRateLimited:
|
||||||
return http.StatusTooManyRequests
|
return http.StatusTooManyRequests
|
||||||
@@ -111,6 +125,12 @@ func HTTPStatus(code Code) int {
|
|||||||
return http.StatusBadGateway
|
return http.StatusBadGateway
|
||||||
case CodeUnavailable:
|
case CodeUnavailable:
|
||||||
return http.StatusServiceUnavailable
|
return http.StatusServiceUnavailable
|
||||||
|
case CodeWorkspaceCloneFailed, CodeGitCmdFailed:
|
||||||
|
return http.StatusBadGateway
|
||||||
|
case CodeWorkspaceRemoteInvalid, CodeWorktreePathInvalid, CodeGitCredentialInvalid:
|
||||||
|
return http.StatusBadRequest
|
||||||
|
case CodeWorktreeNotHeldByCaller:
|
||||||
|
return http.StatusForbidden
|
||||||
default:
|
default:
|
||||||
return http.StatusInternalServerError
|
return http.StatusInternalServerError
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,16 @@ func TestHTTPStatus(t *testing.T) {
|
|||||||
CodeUpstream: http.StatusBadGateway,
|
CodeUpstream: http.StatusBadGateway,
|
||||||
CodeUnavailable: http.StatusServiceUnavailable,
|
CodeUnavailable: http.StatusServiceUnavailable,
|
||||||
CodeInternal: http.StatusInternalServerError,
|
CodeInternal: http.StatusInternalServerError,
|
||||||
|
CodeWorkspaceSlugTaken: http.StatusConflict,
|
||||||
|
CodeWorkspaceSyncInProgress: http.StatusConflict,
|
||||||
|
CodeWorktreeBranchConflict: http.StatusConflict,
|
||||||
|
CodeWorktreeAlreadyActive: http.StatusConflict,
|
||||||
|
CodeWorkspaceCloneFailed: http.StatusBadGateway,
|
||||||
|
CodeGitCmdFailed: http.StatusBadGateway,
|
||||||
|
CodeWorkspaceRemoteInvalid: http.StatusBadRequest,
|
||||||
|
CodeWorktreePathInvalid: http.StatusBadRequest,
|
||||||
|
CodeGitCredentialInvalid: http.StatusBadRequest,
|
||||||
|
CodeWorktreeNotHeldByCaller: http.StatusForbidden,
|
||||||
}
|
}
|
||||||
for code, want := range cases {
|
for code, want := range cases {
|
||||||
require.Equal(t, want, HTTPStatus(code), "code=%s", code)
|
require.Equal(t, want, HTTPStatus(code), "code=%s", code)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
DROP INDEX IF EXISTS idx_requirements_workspace;
|
||||||
|
ALTER TABLE requirements DROP COLUMN IF EXISTS workspace_id;
|
||||||
|
|
||||||
|
DROP INDEX IF EXISTS idx_issues_workspace_status;
|
||||||
|
ALTER TABLE issues DROP CONSTRAINT IF EXISTS issues_project_workspace_fk;
|
||||||
|
ALTER TABLE issues DROP COLUMN IF EXISTS workspace_id;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS git_credentials;
|
||||||
|
DROP TABLE IF EXISTS workspace_worktrees;
|
||||||
|
DROP TABLE IF EXISTS workspaces;
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
-- ============ Workspaces ============
|
||||||
|
CREATE TABLE workspaces (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||||
|
slug TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
git_remote_url TEXT NOT NULL,
|
||||||
|
default_branch TEXT NOT NULL DEFAULT 'main',
|
||||||
|
main_path TEXT NOT NULL,
|
||||||
|
sync_status TEXT NOT NULL DEFAULT 'cloning',
|
||||||
|
last_synced_at TIMESTAMPTZ,
|
||||||
|
last_sync_error TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT workspaces_sync_status_check CHECK (sync_status IN ('cloning','idle','syncing','error')),
|
||||||
|
UNIQUE (project_id, slug),
|
||||||
|
UNIQUE (project_id, id)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_workspaces_project ON workspaces(project_id);
|
||||||
|
|
||||||
|
-- ============ Worktrees ============
|
||||||
|
CREATE TABLE workspace_worktrees (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||||
|
branch TEXT NOT NULL,
|
||||||
|
path TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL DEFAULT 'idle',
|
||||||
|
active_holder TEXT,
|
||||||
|
acquired_at TIMESTAMPTZ,
|
||||||
|
last_used_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT worktrees_status_check CHECK (status IN ('idle','active','pruning')),
|
||||||
|
UNIQUE (workspace_id, branch),
|
||||||
|
UNIQUE (workspace_id, path)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_worktrees_ws_status ON workspace_worktrees(workspace_id, status, last_used_at);
|
||||||
|
|
||||||
|
-- ============ Git credentials ============
|
||||||
|
CREATE TABLE git_credentials (
|
||||||
|
id UUID PRIMARY KEY,
|
||||||
|
workspace_id UUID NOT NULL UNIQUE REFERENCES workspaces(id) ON DELETE CASCADE,
|
||||||
|
kind TEXT NOT NULL,
|
||||||
|
username TEXT,
|
||||||
|
encrypted_secret BYTEA,
|
||||||
|
fingerprint TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
|
CONSTRAINT git_credentials_kind_check CHECK (kind IN ('pat','ssh_key','none'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============ ALTER existing PM tables ============
|
||||||
|
ALTER TABLE issues ADD COLUMN workspace_id UUID NULL;
|
||||||
|
ALTER TABLE issues ADD CONSTRAINT issues_project_workspace_fk
|
||||||
|
FOREIGN KEY (project_id, workspace_id)
|
||||||
|
REFERENCES workspaces (project_id, id) MATCH SIMPLE
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
CREATE INDEX idx_issues_workspace_status ON issues(workspace_id, status);
|
||||||
|
|
||||||
|
ALTER TABLE requirements ADD COLUMN workspace_id UUID NULL
|
||||||
|
REFERENCES workspaces(id) ON DELETE SET NULL;
|
||||||
|
CREATE INDEX idx_requirements_workspace ON requirements(workspace_id);
|
||||||
Reference in New Issue
Block a user