feat: /:code/:variant URL для вариантов опти + валидация имени варианта
- Роут GET /:code/:variant → редирект на /projects/:uuid (case-insensitive) - Валидация имени варианта: только URL-безопасные символы [A-Za-z0-9._-] (бэкенд validateProjectVariantName + клиентская проверка в обеих формах) - Подсказки в UI: «Используется в URL: /КОД/Вариант» Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,13 +17,14 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrProjectNotFound = errors.New("project not found")
|
||||
ErrProjectForbidden = errors.New("access to project forbidden")
|
||||
ErrProjectCodeExists = errors.New("project code and variant already exist")
|
||||
ErrCannotDeleteMainVariant = errors.New("cannot delete main variant")
|
||||
ErrReservedMainVariant = errors.New("variant name 'main' is reserved")
|
||||
ErrCannotRenameMainVariant = errors.New("cannot rename main variant")
|
||||
ErrProjectCodeInvalidChars = errors.New("код опти содержит недопустимые символы (разрешены: буквы, цифры, дефис, точка, подчёркивание)")
|
||||
ErrProjectNotFound = errors.New("project not found")
|
||||
ErrProjectForbidden = errors.New("access to project forbidden")
|
||||
ErrProjectCodeExists = errors.New("project code and variant already exist")
|
||||
ErrCannotDeleteMainVariant = errors.New("cannot delete main variant")
|
||||
ErrReservedMainVariant = errors.New("variant name 'main' is reserved")
|
||||
ErrCannotRenameMainVariant = errors.New("cannot rename main variant")
|
||||
ErrProjectCodeInvalidChars = errors.New("код опти содержит недопустимые символы (разрешены: буквы, цифры, дефис, точка, подчёркивание)")
|
||||
ErrProjectVariantInvalidChars = errors.New("имя варианта содержит недопустимые символы (разрешены: буквы, цифры, дефис, точка, подчёркивание)")
|
||||
)
|
||||
|
||||
// projectCodeRe allows only URL-path-safe characters so project codes can appear directly in URLs.
|
||||
@@ -194,6 +195,9 @@ func validateProjectVariantName(variant string) error {
|
||||
if normalizeProjectVariant(variant) == "main" {
|
||||
return ErrReservedMainVariant
|
||||
}
|
||||
if variant != "" && !projectCodeRe.MatchString(variant) {
|
||||
return ErrProjectVariantInvalidChars
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -302,6 +306,15 @@ func (s *ProjectService) GetByCode(code string) (*models.Project, error) {
|
||||
return localdb.LocalToProject(localProject), nil
|
||||
}
|
||||
|
||||
// GetByCodeAndVariant finds a project by code + variant (both case-insensitive).
|
||||
func (s *ProjectService) GetByCodeAndVariant(code, variant string) (*models.Project, error) {
|
||||
localProject, err := s.localDB.GetProjectByCodeAndVariant(code, variant)
|
||||
if err != nil {
|
||||
return nil, ErrProjectNotFound
|
||||
}
|
||||
return localdb.LocalToProject(localProject), nil
|
||||
}
|
||||
|
||||
func (s *ProjectService) ListConfigurations(projectUUID, ownerUsername, status string) (*ProjectConfigurationsResult, error) {
|
||||
project, err := s.GetByUUID(projectUUID, ownerUsername)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user