package user import ( "context" "errors" "fmt" "tercul/internal/app/authz" "tercul/internal/domain" platform_auth "tercul/internal/platform/auth" "github.com/google/uuid" ) // UserCommands contains the command handlers for the user aggregate. type UserCommands struct { repo domain.UserRepository authzSvc *authz.Service } // NewUserCommands creates a new UserCommands handler. func NewUserCommands(repo domain.UserRepository, authzSvc *authz.Service) *UserCommands { return &UserCommands{ repo: repo, authzSvc: authzSvc, } } // CreateUserInput represents the input for creating a new user. type CreateUserInput struct { Username string Email string Password string FirstName string LastName string Role domain.UserRole } // CreateUser creates a new user. func (c *UserCommands) CreateUser(ctx context.Context, input CreateUserInput) (*domain.User, error) { user := &domain.User{ Username: input.Username, Email: input.Email, Password: input.Password, FirstName: input.FirstName, LastName: input.LastName, Role: input.Role, } err := c.repo.Create(ctx, user) if err != nil { return nil, err } return user, nil } // UpdateUserInput represents the input for updating an existing user. type UpdateUserInput struct { ID uuid.UUID Username *string Email *string Password *string FirstName *string LastName *string DisplayName *string Bio *string AvatarURL *string Role *domain.UserRole Verified *bool Active *bool CountryID *uuid.UUID CityID *uuid.UUID AddressID *uuid.UUID } // UpdateUser updates an existing user. // //nolint:gocyclo // Complex update logic func (c *UserCommands) UpdateUser(ctx context.Context, input UpdateUserInput) (*domain.User, error) { actorID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, domain.ErrUnauthorized } can, err := c.authzSvc.CanUpdateUser(ctx, actorID, input.ID) if err != nil { return nil, err } if !can { return nil, domain.ErrForbidden } user, err := c.repo.GetByID(ctx, input.ID) if err != nil { if errors.Is(err, domain.ErrEntityNotFound) { return nil, fmt.Errorf("%w: user with id %d not found", domain.ErrEntityNotFound, input.ID) } return nil, err } // Apply partial updates if input.Username != nil { user.Username = *input.Username } if input.Email != nil { user.Email = *input.Email } if input.Password != nil { if err := user.SetPassword(*input.Password); err != nil { return nil, err } } if input.FirstName != nil { user.FirstName = *input.FirstName } if input.LastName != nil { user.LastName = *input.LastName } if input.DisplayName != nil { user.DisplayName = *input.DisplayName } if input.Bio != nil { user.Bio = *input.Bio } if input.AvatarURL != nil { user.AvatarURL = *input.AvatarURL } if input.Role != nil { user.Role = *input.Role } if input.Verified != nil { user.Verified = *input.Verified } if input.Active != nil { user.Active = *input.Active } if input.CountryID != nil { user.CountryID = input.CountryID } if input.CityID != nil { user.CityID = input.CityID } if input.AddressID != nil { user.AddressID = input.AddressID } err = c.repo.Update(ctx, user) if err != nil { return nil, err } return user, nil } // DeleteUser deletes a user by ID. func (c *UserCommands) DeleteUser(ctx context.Context, id uuid.UUID) error { actorID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return domain.ErrUnauthorized } can, err := c.authzSvc.CanUpdateUser(ctx, actorID, id) // Re-using CanUpdateUser for deletion if err != nil { return err } if !can { return domain.ErrForbidden } return c.repo.Delete(ctx, id) }