// user/service.go
package user
import (
"context"
"database/sql"
"errors"
"regexp"
contract "github.com/go-mizu/mizu/contract/v2"
)
// API defines user management operations
type API interface {
Create(ctx context.Context, in *CreateInput) (*User, error)
Get(ctx context.Context, in *GetInput) (*User, error)
Delete(ctx context.Context, in *DeleteInput) error
}
// Service implements user.API
type Service struct {
db *sql.DB
}
var _ API = (*Service)(nil)
var emailRegex = regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`)
func (s *Service) Create(ctx context.Context, in *CreateInput) (*User, error) {
// Validate input - check each field
if in.Email == "" {
return nil, contract.ErrInvalidArgument("email is required")
}
if !emailRegex.MatchString(in.Email) {
return nil, contract.ErrInvalidArgument("invalid email format").
WithDetail("field", "email").
WithDetail("value", in.Email)
}
if len(in.Password) < 8 {
return nil, contract.ErrInvalidArgument("password must be at least 8 characters").
WithDetail("field", "password")
}
if in.Name == "" {
return nil, contract.ErrInvalidArgument("name is required")
}
// Check for duplicates
exists, err := s.userExists(ctx, in.Email)
if err != nil {
return nil, contract.ErrInternal("failed to check user").WithCause(err)
}
if exists {
return nil, contract.ErrAlreadyExists("user with this email already exists").
WithDetail("email", in.Email)
}
// Create the user
user, err := s.insertUser(ctx, in)
if err != nil {
return nil, contract.ErrInternal("failed to create user").WithCause(err)
}
return user, nil
}
func (s *Service) Get(ctx context.Context, in *GetInput) (*User, error) {
if in.ID == "" {
return nil, contract.ErrInvalidArgument("id is required")
}
user, err := s.findUser(ctx, in.ID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, contract.ErrNotFound("user not found").
WithDetail("userId", in.ID)
}
return nil, contract.ErrInternal("failed to fetch user").WithCause(err)
}
return user, nil
}
func (s *Service) Delete(ctx context.Context, in *DeleteInput) error {
// Get current user from context (set by auth middleware)
currentUserID, ok := ctx.Value("userID").(string)
if !ok {
return contract.ErrUnauthenticated("not authenticated")
}
// Users can only delete their own account (unless admin)
if in.ID != currentUserID {
isAdmin, _ := ctx.Value("isAdmin").(bool)
if !isAdmin {
return contract.ErrPermissionDenied("you can only delete your own account")
}
}
// Check user exists before deleting
_, err := s.findUser(ctx, in.ID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return contract.ErrNotFound("user not found")
}
return contract.ErrInternal("failed to check user").WithCause(err)
}
// Delete the user
if err := s.deleteUser(ctx, in.ID); err != nil {
return contract.ErrInternal("failed to delete user").WithCause(err)
}
return nil
}
// Helper methods (implementation details)
func (s *Service) userExists(ctx context.Context, email string) (bool, error) { /* ... */ }
func (s *Service) findUser(ctx context.Context, id string) (*User, error) { /* ... */ }
func (s *Service) insertUser(ctx context.Context, in *CreateInput) (*User, error) { /* ... */ }
func (s *Service) deleteUser(ctx context.Context, id string) error { /* ... */ }