feat: add projects flow and consolidate default project handling
This commit is contained in:
@@ -55,6 +55,11 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf
|
||||
}
|
||||
}
|
||||
|
||||
projectUUID, err := s.resolveProjectUUID(ownerUsername, req.ProjectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total := req.Items.Total()
|
||||
if req.ServerCount > 1 {
|
||||
total *= float64(req.ServerCount)
|
||||
@@ -63,6 +68,7 @@ func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConf
|
||||
cfg := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
ProjectUUID: projectUUID,
|
||||
Name: req.Name,
|
||||
Items: req.Items,
|
||||
TotalPrice: &total,
|
||||
@@ -118,6 +124,11 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
projectUUID, err := s.resolveProjectUUID(ownerUsername, req.ProjectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total := req.Items.Total()
|
||||
if req.ServerCount > 1 {
|
||||
total *= float64(req.ServerCount)
|
||||
@@ -125,6 +136,7 @@ func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, re
|
||||
|
||||
// Update fields
|
||||
localCfg.Name = req.Name
|
||||
localCfg.ProjectUUID = projectUUID
|
||||
localCfg.Items = localdb.LocalConfigItems{}
|
||||
for _, item := range req.Items {
|
||||
localCfg.Items = append(localCfg.Items, localdb.LocalConfigItem{
|
||||
@@ -210,10 +222,21 @@ func (s *LocalConfigurationService) Rename(uuid string, ownerUsername string, ne
|
||||
|
||||
// Clone clones a configuration
|
||||
func (s *LocalConfigurationService) Clone(configUUID string, ownerUsername string, newName string) (*models.Configuration, error) {
|
||||
return s.CloneToProject(configUUID, ownerUsername, newName, nil)
|
||||
}
|
||||
|
||||
func (s *LocalConfigurationService) CloneToProject(configUUID string, ownerUsername string, newName string, projectUUID *string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUID(configUUID, ownerUsername)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedProjectUUID := original.ProjectUUID
|
||||
if projectUUID != nil {
|
||||
resolvedProjectUUID, err = s.resolveProjectUUID(ownerUsername, projectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
total := original.Items.Total()
|
||||
if original.ServerCount > 1 {
|
||||
@@ -223,6 +246,7 @@ func (s *LocalConfigurationService) Clone(configUUID string, ownerUsername strin
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
ProjectUUID: resolvedProjectUUID,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
@@ -362,12 +386,18 @@ func (s *LocalConfigurationService) UpdateNoAuth(uuid string, req *CreateConfigR
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
projectUUID, err := s.resolveProjectUUID(localCfg.OriginalUsername, req.ProjectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total := req.Items.Total()
|
||||
if req.ServerCount > 1 {
|
||||
total *= float64(req.ServerCount)
|
||||
}
|
||||
|
||||
localCfg.Name = req.Name
|
||||
localCfg.ProjectUUID = projectUUID
|
||||
localCfg.Items = localdb.LocalConfigItems{}
|
||||
for _, item := range req.Items {
|
||||
localCfg.Items = append(localCfg.Items, localdb.LocalConfigItem{
|
||||
@@ -440,10 +470,21 @@ func (s *LocalConfigurationService) RenameNoAuth(uuid string, newName string) (*
|
||||
|
||||
// CloneNoAuth clones configuration without ownership check
|
||||
func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName string, ownerUsername string) (*models.Configuration, error) {
|
||||
return s.CloneNoAuthToProject(configUUID, newName, ownerUsername, nil)
|
||||
}
|
||||
|
||||
func (s *LocalConfigurationService) CloneNoAuthToProject(configUUID string, newName string, ownerUsername string, projectUUID *string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUIDNoAuth(configUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolvedProjectUUID := original.ProjectUUID
|
||||
if projectUUID != nil {
|
||||
resolvedProjectUUID, err = s.resolveProjectUUID(ownerUsername, projectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
total := original.Items.Total()
|
||||
if original.ServerCount > 1 {
|
||||
@@ -453,6 +494,7 @@ func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName strin
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
ProjectUUID: resolvedProjectUUID,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
@@ -471,24 +513,59 @@ func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName strin
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
// SetProjectNoAuth moves configuration to a different project without ownership check.
|
||||
func (s *LocalConfigurationService) SetProjectNoAuth(uuid string, projectUUID string) (*models.Configuration, error) {
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
var resolved *string
|
||||
trimmed := strings.TrimSpace(projectUUID)
|
||||
if trimmed == "" {
|
||||
resolved, err = s.resolveProjectUUID(localCfg.OriginalUsername, &projectUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
project, getErr := s.localDB.GetProjectByUUID(trimmed)
|
||||
if getErr != nil {
|
||||
return nil, ErrProjectNotFound
|
||||
}
|
||||
if !project.IsActive {
|
||||
return nil, errors.New("project is archived")
|
||||
}
|
||||
resolved = &project.UUID
|
||||
}
|
||||
|
||||
localCfg.ProjectUUID = resolved
|
||||
localCfg.UpdatedAt = time.Now()
|
||||
localCfg.SyncStatus = "pending"
|
||||
return s.saveWithVersionAndPending(localCfg, "update", "")
|
||||
}
|
||||
|
||||
// ListAll returns all configurations without user filter
|
||||
func (s *LocalConfigurationService) ListAll(page, perPage int) ([]models.Configuration, int64, error) {
|
||||
return s.ListAllWithStatus(page, perPage, "active")
|
||||
return s.ListAllWithStatus(page, perPage, "active", "")
|
||||
}
|
||||
|
||||
// ListAllWithStatus returns configurations filtered by status: active|archived|all.
|
||||
func (s *LocalConfigurationService) ListAllWithStatus(page, perPage int, status string) ([]models.Configuration, int64, error) {
|
||||
func (s *LocalConfigurationService) ListAllWithStatus(page, perPage int, status string, search string) ([]models.Configuration, int64, error) {
|
||||
localConfigs, err := s.localDB.GetConfigurations()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
search = strings.ToLower(strings.TrimSpace(search))
|
||||
configs := make([]models.Configuration, len(localConfigs))
|
||||
configs = configs[:0]
|
||||
for _, lc := range localConfigs {
|
||||
if !matchesConfigStatus(lc.IsActive, status) {
|
||||
continue
|
||||
}
|
||||
if search != "" && !strings.Contains(strings.ToLower(lc.Name), search) {
|
||||
continue
|
||||
}
|
||||
configs = append(configs, *localdb.LocalToConfiguration(&lc))
|
||||
}
|
||||
|
||||
@@ -960,6 +1037,7 @@ func (s *LocalConfigurationService) enqueueConfigurationPendingChangeTx(
|
||||
EventID: uuid.New().String(),
|
||||
IdempotencyKey: fmt.Sprintf("%s:v%d:%s", localCfg.UUID, version.VersionNo, operation),
|
||||
ConfigurationUUID: localCfg.UUID,
|
||||
ProjectUUID: localCfg.ProjectUUID,
|
||||
Operation: operation,
|
||||
CurrentVersionID: version.ID,
|
||||
CurrentVersionNo: version.VersionNo,
|
||||
@@ -1013,3 +1091,28 @@ func matchesConfigStatus(isActive bool, status string) bool {
|
||||
return isActive
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LocalConfigurationService) resolveProjectUUID(ownerUsername string, projectUUID *string) (*string, error) {
|
||||
if ownerUsername == "" {
|
||||
ownerUsername = s.localDB.GetDBUser()
|
||||
}
|
||||
|
||||
if projectUUID == nil || strings.TrimSpace(*projectUUID) == "" {
|
||||
project, err := s.localDB.EnsureDefaultProject(ownerUsername)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &project.UUID, nil
|
||||
}
|
||||
|
||||
requested := strings.TrimSpace(*projectUUID)
|
||||
project, err := s.localDB.GetProjectByUUID(requested)
|
||||
if err != nil {
|
||||
return nil, ErrProjectNotFound
|
||||
}
|
||||
if !project.IsActive {
|
||||
return nil, errors.New("project is archived")
|
||||
}
|
||||
|
||||
return &project.UUID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user