Add project variants and UI updates

This commit is contained in:
Mikhail Chusavitin
2026-02-13 19:27:48 +03:00
parent 4e1a46bd71
commit 9b5d57902d
23 changed files with 1113 additions and 147 deletions

View File

@@ -18,7 +18,7 @@ import (
var (
ErrProjectNotFound = errors.New("project not found")
ErrProjectForbidden = errors.New("access to project forbidden")
ErrProjectNameExists = errors.New("project name already exists")
ErrProjectCodeExists = errors.New("project code and variant already exist")
)
type ProjectService struct {
@@ -30,12 +30,16 @@ func NewProjectService(localDB *localdb.LocalDB) *ProjectService {
}
type CreateProjectRequest struct {
Name string `json:"name"`
Code string `json:"code"`
Variant string `json:"variant,omitempty"`
Name *string `json:"name,omitempty"`
TrackerURL string `json:"tracker_url"`
}
type UpdateProjectRequest struct {
Name string `json:"name"`
Code *string `json:"code,omitempty"`
Variant *string `json:"variant,omitempty"`
Name *string `json:"name,omitempty"`
TrackerURL *string `json:"tracker_url,omitempty"`
}
@@ -46,11 +50,19 @@ type ProjectConfigurationsResult struct {
}
func (s *ProjectService) Create(ownerUsername string, req *CreateProjectRequest) (*models.Project, error) {
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, fmt.Errorf("project name is required")
var namePtr *string
if req.Name != nil {
name := strings.TrimSpace(*req.Name)
if name != "" {
namePtr = &name
}
}
if err := s.ensureUniqueProjectName("", name); err != nil {
code := strings.TrimSpace(req.Code)
if code == "" {
return nil, fmt.Errorf("project code is required")
}
variant := strings.TrimSpace(req.Variant)
if err := s.ensureUniqueProjectCodeVariant("", code, variant); err != nil {
return nil, err
}
@@ -58,8 +70,10 @@ func (s *ProjectService) Create(ownerUsername string, req *CreateProjectRequest)
localProject := &localdb.LocalProject{
UUID: uuid.NewString(),
OwnerUsername: ownerUsername,
Name: name,
TrackerURL: normalizeProjectTrackerURL(name, req.TrackerURL),
Code: code,
Variant: variant,
Name: namePtr,
TrackerURL: normalizeProjectTrackerURL(code, req.TrackerURL),
IsActive: true,
IsSystem: false,
CreatedAt: now,
@@ -81,19 +95,32 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
return nil, ErrProjectNotFound
}
name := strings.TrimSpace(req.Name)
if name == "" {
return nil, fmt.Errorf("project name is required")
if req.Code != nil {
code := strings.TrimSpace(*req.Code)
if code == "" {
return nil, fmt.Errorf("project code is required")
}
localProject.Code = code
}
if err := s.ensureUniqueProjectName(projectUUID, name); err != nil {
if req.Variant != nil {
localProject.Variant = strings.TrimSpace(*req.Variant)
}
if err := s.ensureUniqueProjectCodeVariant(projectUUID, localProject.Code, localProject.Variant); err != nil {
return nil, err
}
localProject.Name = name
if req.Name != nil {
name := strings.TrimSpace(*req.Name)
if name == "" {
localProject.Name = nil
} else {
localProject.Name = &name
}
}
if req.TrackerURL != nil {
localProject.TrackerURL = normalizeProjectTrackerURL(name, *req.TrackerURL)
localProject.TrackerURL = normalizeProjectTrackerURL(localProject.Code, *req.TrackerURL)
} else if strings.TrimSpace(localProject.TrackerURL) == "" {
localProject.TrackerURL = normalizeProjectTrackerURL(name, "")
localProject.TrackerURL = normalizeProjectTrackerURL(localProject.Code, "")
}
localProject.UpdatedAt = time.Now()
localProject.SyncStatus = "pending"
@@ -106,10 +133,11 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
return localdb.LocalToProject(localProject), nil
}
func (s *ProjectService) ensureUniqueProjectName(excludeUUID, name string) error {
normalized := normalizeProjectName(name)
if normalized == "" {
return fmt.Errorf("project name is required")
func (s *ProjectService) ensureUniqueProjectCodeVariant(excludeUUID, code, variant string) error {
normalizedCode := normalizeProjectCode(code)
normalizedVariant := normalizeProjectVariant(variant)
if normalizedCode == "" {
return fmt.Errorf("project code is required")
}
projects, err := s.localDB.GetAllProjects(true)
@@ -121,15 +149,20 @@ func (s *ProjectService) ensureUniqueProjectName(excludeUUID, name string) error
if excludeUUID != "" && project.UUID == excludeUUID {
continue
}
if normalizeProjectName(project.Name) == normalized {
return ErrProjectNameExists
if normalizeProjectCode(project.Code) == normalizedCode &&
normalizeProjectVariant(project.Variant) == normalizedVariant {
return ErrProjectCodeExists
}
}
return nil
}
func normalizeProjectName(name string) string {
return strings.ToLower(strings.TrimSpace(name))
func normalizeProjectCode(code string) string {
return strings.ToLower(strings.TrimSpace(code))
}
func normalizeProjectVariant(variant string) string {
return strings.ToLower(strings.TrimSpace(variant))
}
func (s *ProjectService) Archive(projectUUID, ownerUsername string) error {