Fix project selection and add project settings UI
This commit is contained in:
@@ -147,8 +147,8 @@ func (h *WebHandler) render(c *gin.Context, name string, data gin.H) {
|
||||
}
|
||||
|
||||
func (h *WebHandler) Index(c *gin.Context) {
|
||||
// Redirect to configs page - configurator is accessed via /configurator?uuid=...
|
||||
c.Redirect(302, "/configs")
|
||||
// Redirect to projects page - configurator is accessed via /configurator?uuid=...
|
||||
c.Redirect(302, "/projects")
|
||||
}
|
||||
|
||||
func (h *WebHandler) Configurator(c *gin.Context) {
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.mchus.pro/mchus/quoteforge/internal/appstate"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/appmeta"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/appstate"
|
||||
"github.com/glebarez/sqlite"
|
||||
mysqlDriver "github.com/go-sql-driver/mysql"
|
||||
uuidpkg "github.com/google/uuid"
|
||||
@@ -434,18 +434,36 @@ func (l *LocalDB) ListConfigurationsWithFilters(status string, search string, of
|
||||
query := l.db.Model(&LocalConfiguration{})
|
||||
switch status {
|
||||
case "active":
|
||||
query = query.Where("is_active = ?", true)
|
||||
query = query.Where("local_configurations.is_active = ?", true)
|
||||
case "archived":
|
||||
query = query.Where("is_active = ?", false)
|
||||
query = query.Where("local_configurations.is_active = ?", false)
|
||||
case "all", "":
|
||||
// no-op
|
||||
default:
|
||||
query = query.Where("is_active = ?", true)
|
||||
query = query.Where("local_configurations.is_active = ?", true)
|
||||
}
|
||||
|
||||
search = strings.TrimSpace(search)
|
||||
if search != "" {
|
||||
query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(search)+"%")
|
||||
needle := "%" + strings.ToLower(search) + "%"
|
||||
hasProjectsTable := l.db.Migrator().HasTable(&LocalProject{})
|
||||
hasServerModel := l.db.Migrator().HasColumn(&LocalConfiguration{}, "server_model")
|
||||
|
||||
conditions := []string{"LOWER(local_configurations.name) LIKE ?"}
|
||||
args := []interface{}{needle}
|
||||
|
||||
if hasProjectsTable {
|
||||
query = query.Joins("LEFT JOIN local_projects lp ON lp.uuid = local_configurations.project_uuid")
|
||||
conditions = append(conditions, "LOWER(COALESCE(lp.name, '')) LIKE ?")
|
||||
args = append(args, needle)
|
||||
}
|
||||
|
||||
if hasServerModel {
|
||||
conditions = append(conditions, "LOWER(COALESCE(local_configurations.server_model, '')) LIKE ?")
|
||||
args = append(args, needle)
|
||||
}
|
||||
|
||||
query = query.Where(strings.Join(conditions, " OR "), args...)
|
||||
}
|
||||
|
||||
var total int64
|
||||
@@ -454,7 +472,7 @@ func (l *LocalDB) ListConfigurationsWithFilters(status string, search string, of
|
||||
}
|
||||
|
||||
var configs []LocalConfiguration
|
||||
if err := query.Order("created_at DESC").Offset(offset).Limit(limit).Find(&configs).Error; err != nil {
|
||||
if err := query.Order("local_configurations.created_at DESC").Offset(offset).Limit(limit).Find(&configs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return configs, total, nil
|
||||
|
||||
@@ -16,8 +16,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProjectNotFound = errors.New("project not found")
|
||||
ErrProjectForbidden = errors.New("access to project forbidden")
|
||||
ErrProjectNotFound = errors.New("project not found")
|
||||
ErrProjectForbidden = errors.New("access to project forbidden")
|
||||
ErrProjectNameExists = errors.New("project name already exists")
|
||||
)
|
||||
|
||||
type ProjectService struct {
|
||||
@@ -49,6 +50,9 @@ func (s *ProjectService) Create(ownerUsername string, req *CreateProjectRequest)
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("project name is required")
|
||||
}
|
||||
if err := s.ensureUniqueProjectName("", name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
localProject := &localdb.LocalProject{
|
||||
@@ -81,6 +85,9 @@ func (s *ProjectService) Update(projectUUID, ownerUsername string, req *UpdatePr
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("project name is required")
|
||||
}
|
||||
if err := s.ensureUniqueProjectName(projectUUID, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localProject.Name = name
|
||||
if req.TrackerURL != nil {
|
||||
@@ -99,6 +106,32 @@ 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")
|
||||
}
|
||||
|
||||
projects, err := s.localDB.GetAllProjects(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range projects {
|
||||
project := projects[i]
|
||||
if excludeUUID != "" && project.UUID == excludeUUID {
|
||||
continue
|
||||
}
|
||||
if normalizeProjectName(project.Name) == normalized {
|
||||
return ErrProjectNameExists
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizeProjectName(name string) string {
|
||||
return strings.ToLower(strings.TrimSpace(name))
|
||||
}
|
||||
|
||||
func (s *ProjectService) Archive(projectUUID, ownerUsername string) error {
|
||||
return s.setProjectActive(projectUUID, ownerUsername, false)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user