package user import ( "context" "errors" "example.com/userService/internal/apierrors" "example.com/userService/internal/model" repo "example.com/userService/internal/repository/user" "example.com/userService/internal/servicecontext" "example.com/userService/internal/types" omnierrors "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.UserNotFound() case errors.Is(err, model.ErrEmailTaken): return apierrors.EmailTaken() case errors.Is(err, model.ErrInvalidUser): return apierrors.InvalidUser() case errors.Is(err, model.ErrInvalidPage): return apierrors.InvalidPage() 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 omnierrors.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 }