feat(project): migration 0002 + errs codes for project/requirement/issue

This commit is contained in:
2026-05-01 09:31:03 +08:00
parent 357d08f2e3
commit f261504c51
3 changed files with 67 additions and 1 deletions
+7 -1
View File
@@ -27,6 +27,11 @@ const (
CodeUpstream Code = "upstream_error"
CodeUnavailable Code = "unavailable"
CodeInternal Code = "internal"
// Project 模块专用细粒度 code(HTTP 仍 409,但前端可据此区分提示)。
CodeProjectSlugTaken Code = "project_slug_taken"
CodeProjectArchived Code = "project_archived"
CodeRequirementClosed Code = "requirement_closed"
)
// AppError is the application's structured error type. It carries a stable
@@ -97,7 +102,8 @@ func HTTPStatus(code Code) int {
return http.StatusForbidden
case CodeNotFound:
return http.StatusNotFound
case CodeConflict:
case CodeConflict,
CodeProjectSlugTaken, CodeProjectArchived, CodeRequirementClosed:
return http.StatusConflict
case CodeRateLimited:
return http.StatusTooManyRequests
+3
View File
@@ -0,0 +1,3 @@
DROP TABLE IF EXISTS issues;
DROP TABLE IF EXISTS requirements;
DROP TABLE IF EXISTS projects;
+57
View File
@@ -0,0 +1,57 @@
-- ============ Project ============
CREATE TABLE projects (
id UUID PRIMARY KEY,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
visibility TEXT NOT NULL DEFAULT 'private',
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
archived_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT projects_visibility_check CHECK (visibility IN ('private','internal'))
);
CREATE INDEX idx_projects_owner ON projects(owner_id);
CREATE INDEX idx_projects_archived ON projects(archived_at) WHERE archived_at IS NULL;
-- ============ Requirement ============
CREATE TABLE requirements (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
number INT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
phase TEXT NOT NULL DEFAULT 'planning',
status TEXT NOT NULL DEFAULT 'open',
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
closed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (project_id, number),
CONSTRAINT requirements_phase_check CHECK (phase IN ('planning','auditing','implementing','reviewing','done')),
CONSTRAINT requirements_status_check CHECK (status IN ('open','closed'))
);
CREATE INDEX idx_requirements_project_phase ON requirements(project_id, phase);
CREATE INDEX idx_requirements_project_status ON requirements(project_id, status);
CREATE INDEX idx_requirements_owner ON requirements(owner_id);
-- ============ Issue ============
CREATE TABLE issues (
id UUID PRIMARY KEY,
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
requirement_id UUID REFERENCES requirements(id) ON DELETE SET NULL,
number INT NOT NULL,
title TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'open',
assignee_id UUID REFERENCES users(id) ON DELETE SET NULL,
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
closed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (project_id, number),
CONSTRAINT issues_status_check CHECK (status IN ('open','closed'))
);
CREATE INDEX idx_issues_requirement ON issues(requirement_id) WHERE requirement_id IS NOT NULL;
CREATE INDEX idx_issues_project_status ON issues(project_id, status);
CREATE INDEX idx_issues_assignee ON issues(assignee_id);