Store configuration owner by MariaDB username
This commit is contained in:
@@ -4,20 +4,20 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/models"
|
||||
"git.mchus.pro/mchus/quoteforge/internal/repository"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConfigNotFound = errors.New("configuration not found")
|
||||
ErrConfigForbidden = errors.New("access to configuration forbidden")
|
||||
ErrConfigNotFound = errors.New("configuration not found")
|
||||
ErrConfigForbidden = errors.New("access to configuration forbidden")
|
||||
)
|
||||
|
||||
// ConfigurationGetter is an interface for services that can retrieve configurations
|
||||
// Used by handlers to work with both ConfigurationService and LocalConfigurationService
|
||||
type ConfigurationGetter interface {
|
||||
GetByUUID(uuid string, userID uint) (*models.Configuration, error)
|
||||
GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error)
|
||||
}
|
||||
|
||||
type ConfigurationService struct {
|
||||
@@ -39,15 +39,15 @@ func NewConfigurationService(
|
||||
}
|
||||
|
||||
type CreateConfigRequest struct {
|
||||
Name string `json:"name"`
|
||||
Items models.ConfigItems `json:"items"`
|
||||
CustomPrice *float64 `json:"custom_price"`
|
||||
Notes string `json:"notes"`
|
||||
IsTemplate bool `json:"is_template"`
|
||||
ServerCount int `json:"server_count"`
|
||||
Name string `json:"name"`
|
||||
Items models.ConfigItems `json:"items"`
|
||||
CustomPrice *float64 `json:"custom_price"`
|
||||
Notes string `json:"notes"`
|
||||
IsTemplate bool `json:"is_template"`
|
||||
ServerCount int `json:"server_count"`
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) Create(userID uint, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) Create(ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
total := req.Items.Total()
|
||||
|
||||
// If server count is greater than 1, multiply the total by server count
|
||||
@@ -56,15 +56,15 @@ func (s *ConfigurationService) Create(userID uint, req *CreateConfigRequest) (*m
|
||||
}
|
||||
|
||||
config := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Items: req.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: req.CustomPrice,
|
||||
Notes: req.Notes,
|
||||
IsTemplate: req.IsTemplate,
|
||||
ServerCount: req.ServerCount,
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: req.Name,
|
||||
Items: req.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: req.CustomPrice,
|
||||
Notes: req.Notes,
|
||||
IsTemplate: req.IsTemplate,
|
||||
ServerCount: req.ServerCount,
|
||||
}
|
||||
|
||||
if err := s.configRepo.Create(config); err != nil {
|
||||
@@ -77,27 +77,27 @@ func (s *ConfigurationService) Create(userID uint, req *CreateConfigRequest) (*m
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) GetByUUID(uuid string, userID uint) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
|
||||
config, err := s.configRepo.GetByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
// Allow access if user owns config or it's a template
|
||||
if config.UserID != userID && !config.IsTemplate {
|
||||
if !s.isOwner(config, ownerUsername) && !config.IsTemplate {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) Update(uuid string, userID uint, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) Update(uuid string, ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
config, err := s.configRepo.GetByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
if config.UserID != userID {
|
||||
if !s.isOwner(config, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -123,26 +123,26 @@ func (s *ConfigurationService) Update(uuid string, userID uint, req *CreateConfi
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) Delete(uuid string, userID uint) error {
|
||||
func (s *ConfigurationService) Delete(uuid string, ownerUsername string) error {
|
||||
config, err := s.configRepo.GetByUUID(uuid)
|
||||
if err != nil {
|
||||
return ErrConfigNotFound
|
||||
}
|
||||
|
||||
if config.UserID != userID {
|
||||
if !s.isOwner(config, ownerUsername) {
|
||||
return ErrConfigForbidden
|
||||
}
|
||||
|
||||
return s.configRepo.Delete(config.ID)
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) Rename(uuid string, userID uint, newName string) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) Rename(uuid string, ownerUsername string, newName string) (*models.Configuration, error) {
|
||||
config, err := s.configRepo.GetByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
if config.UserID != userID {
|
||||
if !s.isOwner(config, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -155,8 +155,8 @@ func (s *ConfigurationService) Rename(uuid string, userID uint, newName string)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) Clone(configUUID string, userID uint, newName string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUID(configUUID, userID)
|
||||
func (s *ConfigurationService) Clone(configUUID string, ownerUsername string, newName string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUID(configUUID, ownerUsername)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -170,15 +170,15 @@ func (s *ConfigurationService) Clone(configUUID string, userID uint, newName str
|
||||
}
|
||||
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false, // Clone is never a template
|
||||
ServerCount: original.ServerCount,
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false, // Clone is never a template
|
||||
ServerCount: original.ServerCount,
|
||||
}
|
||||
|
||||
if err := s.configRepo.Create(clone); err != nil {
|
||||
@@ -188,7 +188,7 @@ func (s *ConfigurationService) Clone(configUUID string, userID uint, newName str
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) ListByUser(userID uint, page, perPage int) ([]models.Configuration, int64, error) {
|
||||
func (s *ConfigurationService) ListByUser(ownerUsername string, page, perPage int) ([]models.Configuration, int64, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func (s *ConfigurationService) ListByUser(userID uint, page, perPage int) ([]mod
|
||||
}
|
||||
offset := (page - 1) * perPage
|
||||
|
||||
return s.configRepo.ListByUser(userID, offset, perPage)
|
||||
return s.configRepo.ListByUser(ownerUsername, offset, perPage)
|
||||
}
|
||||
|
||||
// ListAll returns all configurations without user filter (for use when auth is disabled)
|
||||
@@ -274,7 +274,7 @@ func (s *ConfigurationService) RenameNoAuth(uuid string, newName string) (*model
|
||||
}
|
||||
|
||||
// CloneNoAuth clones configuration without ownership check
|
||||
func (s *ConfigurationService) CloneNoAuth(configUUID string, newName string, userID uint) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) CloneNoAuth(configUUID string, newName string, ownerUsername string) (*models.Configuration, error) {
|
||||
original, err := s.configRepo.GetByUUID(configUUID)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
@@ -286,15 +286,15 @@ func (s *ConfigurationService) CloneNoAuth(configUUID string, newName string, us
|
||||
}
|
||||
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID, // Use provided user ID
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
}
|
||||
|
||||
if err := s.configRepo.Create(clone); err != nil {
|
||||
@@ -356,13 +356,13 @@ func (s *ConfigurationService) ListTemplates(page, perPage int) ([]models.Config
|
||||
}
|
||||
|
||||
// RefreshPrices updates all component prices in the configuration with current prices
|
||||
func (s *ConfigurationService) RefreshPrices(uuid string, userID uint) (*models.Configuration, error) {
|
||||
func (s *ConfigurationService) RefreshPrices(uuid string, ownerUsername string) (*models.Configuration, error) {
|
||||
config, err := s.configRepo.GetByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
if config.UserID != userID {
|
||||
if !s.isOwner(config, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -407,6 +407,19 @@ func (s *ConfigurationService) RefreshPrices(uuid string, userID uint) (*models.
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func (s *ConfigurationService) isOwner(config *models.Configuration, ownerUsername string) bool {
|
||||
if config == nil || ownerUsername == "" {
|
||||
return false
|
||||
}
|
||||
if config.OwnerUsername != "" {
|
||||
return config.OwnerUsername == ownerUsername
|
||||
}
|
||||
if config.User != nil {
|
||||
return config.User.Username == ownerUsername
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// // Export configuration as JSON
|
||||
// type ConfigExport struct {
|
||||
// Name string `json:"name"`
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewLocalConfigurationService(
|
||||
}
|
||||
|
||||
// Create creates a new configuration in local SQLite and queues it for sync
|
||||
func (s *LocalConfigurationService) Create(userID uint, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) Create(ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
// If online, check for new pricelists first
|
||||
if s.isOnline() {
|
||||
if err := s.syncService.SyncPricelistsIfNeeded(); err != nil {
|
||||
@@ -49,16 +49,16 @@ func (s *LocalConfigurationService) Create(userID uint, req *CreateConfigRequest
|
||||
}
|
||||
|
||||
cfg := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
Items: req.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: req.CustomPrice,
|
||||
Notes: req.Notes,
|
||||
IsTemplate: req.IsTemplate,
|
||||
ServerCount: req.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: req.Name,
|
||||
Items: req.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: req.CustomPrice,
|
||||
Notes: req.Notes,
|
||||
IsTemplate: req.IsTemplate,
|
||||
ServerCount: req.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Convert to local model
|
||||
@@ -85,7 +85,7 @@ func (s *LocalConfigurationService) Create(userID uint, req *CreateConfigRequest
|
||||
}
|
||||
|
||||
// GetByUUID returns a configuration from local SQLite
|
||||
func (s *LocalConfigurationService) GetByUUID(uuid string, userID uint) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) GetByUUID(uuid string, ownerUsername string) (*models.Configuration, error) {
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
@@ -95,7 +95,7 @@ func (s *LocalConfigurationService) GetByUUID(uuid string, userID uint) (*models
|
||||
cfg := localdb.LocalToConfiguration(localCfg)
|
||||
|
||||
// Allow access if user owns config or it's a template
|
||||
if cfg.UserID != userID && !cfg.IsTemplate {
|
||||
if !s.isOwner(localCfg, ownerUsername) && !cfg.IsTemplate {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -103,13 +103,13 @@ func (s *LocalConfigurationService) GetByUUID(uuid string, userID uint) (*models
|
||||
}
|
||||
|
||||
// Update updates a configuration in local SQLite and queues it for sync
|
||||
func (s *LocalConfigurationService) Update(uuid string, userID uint, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) Update(uuid string, ownerUsername string, req *CreateConfigRequest) (*models.Configuration, error) {
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
if localCfg.OriginalUserID != userID {
|
||||
if !s.isOwner(localCfg, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -155,13 +155,13 @@ func (s *LocalConfigurationService) Update(uuid string, userID uint, req *Create
|
||||
}
|
||||
|
||||
// Delete deletes a configuration from local SQLite and queues it for sync
|
||||
func (s *LocalConfigurationService) Delete(uuid string, userID uint) error {
|
||||
func (s *LocalConfigurationService) Delete(uuid string, ownerUsername string) error {
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
return ErrConfigNotFound
|
||||
}
|
||||
|
||||
if localCfg.OriginalUserID != userID {
|
||||
if !s.isOwner(localCfg, ownerUsername) {
|
||||
return ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -179,13 +179,13 @@ func (s *LocalConfigurationService) Delete(uuid string, userID uint) error {
|
||||
}
|
||||
|
||||
// Rename renames a configuration
|
||||
func (s *LocalConfigurationService) Rename(uuid string, userID uint, newName string) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) Rename(uuid string, ownerUsername string, newName string) (*models.Configuration, error) {
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
return nil, ErrConfigNotFound
|
||||
}
|
||||
|
||||
if localCfg.OriginalUserID != userID {
|
||||
if !s.isOwner(localCfg, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -211,8 +211,8 @@ func (s *LocalConfigurationService) Rename(uuid string, userID uint, newName str
|
||||
}
|
||||
|
||||
// Clone clones a configuration
|
||||
func (s *LocalConfigurationService) Clone(configUUID string, userID uint, newName string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUID(configUUID, userID)
|
||||
func (s *LocalConfigurationService) Clone(configUUID string, ownerUsername string, newName string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUID(configUUID, ownerUsername)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -223,16 +223,16 @@ func (s *LocalConfigurationService) Clone(configUUID string, userID uint, newNam
|
||||
}
|
||||
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
localCfg := localdb.ConfigurationToLocal(clone)
|
||||
@@ -253,7 +253,7 @@ func (s *LocalConfigurationService) Clone(configUUID string, userID uint, newNam
|
||||
}
|
||||
|
||||
// ListByUser returns all configurations for a user from local SQLite
|
||||
func (s *LocalConfigurationService) ListByUser(userID uint, page, perPage int) ([]models.Configuration, int64, error) {
|
||||
func (s *LocalConfigurationService) ListByUser(ownerUsername string, page, perPage int) ([]models.Configuration, int64, error) {
|
||||
// Get all local configurations
|
||||
localConfigs, err := s.localDB.GetConfigurations()
|
||||
if err != nil {
|
||||
@@ -263,7 +263,7 @@ func (s *LocalConfigurationService) ListByUser(userID uint, page, perPage int) (
|
||||
// Filter by user
|
||||
var userConfigs []models.Configuration
|
||||
for _, lc := range localConfigs {
|
||||
if lc.OriginalUserID == userID || lc.IsTemplate {
|
||||
if (lc.OriginalUsername == ownerUsername) || lc.IsTemplate {
|
||||
userConfigs = append(userConfigs, *localdb.LocalToConfiguration(&lc))
|
||||
}
|
||||
}
|
||||
@@ -292,7 +292,7 @@ func (s *LocalConfigurationService) ListByUser(userID uint, page, perPage int) (
|
||||
}
|
||||
|
||||
// RefreshPrices updates all component prices in the configuration from local cache
|
||||
func (s *LocalConfigurationService) RefreshPrices(uuid string, userID uint) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) RefreshPrices(uuid string, ownerUsername string) (*models.Configuration, error) {
|
||||
// Get configuration from local SQLite
|
||||
localCfg, err := s.localDB.GetConfigurationByUUID(uuid)
|
||||
if err != nil {
|
||||
@@ -300,7 +300,7 @@ func (s *LocalConfigurationService) RefreshPrices(uuid string, userID uint) (*mo
|
||||
}
|
||||
|
||||
// Check ownership
|
||||
if localCfg.OriginalUserID != userID {
|
||||
if !s.isOwner(localCfg, ownerUsername) {
|
||||
return nil, ErrConfigForbidden
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ func (s *LocalConfigurationService) RenameNoAuth(uuid string, newName string) (*
|
||||
}
|
||||
|
||||
// CloneNoAuth clones configuration without ownership check
|
||||
func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName string, userID uint) (*models.Configuration, error) {
|
||||
func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName string, ownerUsername string) (*models.Configuration, error) {
|
||||
original, err := s.GetByUUIDNoAuth(configUUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -460,16 +460,16 @@ func (s *LocalConfigurationService) CloneNoAuth(configUUID string, newName strin
|
||||
}
|
||||
|
||||
clone := &models.Configuration{
|
||||
UUID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
UUID: uuid.New().String(),
|
||||
OwnerUsername: ownerUsername,
|
||||
Name: newName,
|
||||
Items: original.Items,
|
||||
TotalPrice: &total,
|
||||
CustomPrice: original.CustomPrice,
|
||||
Notes: original.Notes,
|
||||
IsTemplate: false,
|
||||
ServerCount: original.ServerCount,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
localCfg := localdb.ConfigurationToLocal(clone)
|
||||
@@ -626,3 +626,13 @@ func (s *LocalConfigurationService) RefreshPricesNoAuth(uuid string) (*models.Co
|
||||
func (s *LocalConfigurationService) ImportFromServer() (*sync.ConfigImportResult, error) {
|
||||
return s.syncService.ImportConfigurationsToLocal()
|
||||
}
|
||||
|
||||
func (s *LocalConfigurationService) isOwner(cfg *localdb.LocalConfiguration, ownerUsername string) bool {
|
||||
if cfg == nil || ownerUsername == "" {
|
||||
return false
|
||||
}
|
||||
if cfg.OriginalUsername != "" {
|
||||
return cfg.OriginalUsername == ownerUsername
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user