Files
go-omni/examples/userService/internal/service/user/service.go

68 lines
2.6 KiB
Go

package user
import (
"context"
"errors"
"example.com/userService/internal/model"
repo "example.com/userService/internal/repository/user"
"example.com/userService/internal/servicecontext"
"example.com/userService/internal/types"
apierrors "git.yuntuo.hk/k2safe/go-omni/errors"
"git.yuntuo.hk/k2safe/go-omni/logx"
"net/mail"
"strings"
"time"
"unicode/utf8"
)
type Service struct{ deps *servicecontext.Context }
func New(deps *servicecontext.Context) *Service { return &Service{deps: deps} }
func normalize(name, email string) (model.UserInput, error) {
input := model.UserInput{Name: strings.TrimSpace(name), Email: strings.ToLower(strings.TrimSpace(email))}
address, err := mail.ParseAddress(input.Email)
if input.Name == "" || utf8.RuneCountInString(input.Name) > 100 || len(input.Email) > 254 || err != nil || address.Address != input.Email {
return model.UserInput{}, model.ErrInvalidUser
}
return input, nil
}
func response(u model.User) *types.UserResp {
return &types.UserResp{ID: u.ID, Name: u.Name, Email: u.Email, CreatedAt: u.CreatedAt.UTC().Format(time.RFC3339Nano), UpdatedAt: u.UpdatedAt.UTC().Format(time.RFC3339Nano)}
}
func (s *Service) failure(ctx context.Context, err error) error {
switch {
case err == nil:
return nil
case errors.Is(err, model.ErrUserNotFound):
return apierrors.NotFound("USER_NOT_FOUND", "user not found")
case errors.Is(err, model.ErrEmailTaken):
return apierrors.New(409, "EMAIL_TAKEN", "email already in use")
case errors.Is(err, model.ErrInvalidUser):
return apierrors.BadRequest("INVALID_USER", "invalid user")
case errors.Is(err, model.ErrInvalidPage):
return apierrors.BadRequest("INVALID_PAGE", "invalid pagination")
default:
// Do not log SQL/DSN or user input; requestId correlates the failed operation.
s.deps.Logger.ErrorContext(ctx, "user.operationFailed", "category", "persistence")
// HTTP access logging also sees the returned error. Keep raw driver
// errors out of that path because their messages may contain input.
return apierrors.New(500, "INTERNAL_ERROR", "internal server error")
}
}
func (s *Service) mutate(ctx context.Context, operation string, fn func(repo.UserRepository) (model.User, error)) (*types.UserResp, error) {
var u model.User
err := s.deps.Users.WithTx(ctx, func(r repo.UserRepository, audit func(string, int64, string) error) error {
var err error
u, err = fn(r)
if err != nil {
return err
}
return audit(operation, u.ID, logx.RequestID(ctx))
})
if err != nil {
return nil, s.failure(ctx, err)
}
s.deps.Logger.InfoContext(ctx, "user."+operation, "userId", u.ID)
return response(u), nil
}