spark-server/internal/module/user/logic/profile.go

189 lines
5.7 KiB
Go

package logic
import (
"context"
"strings"
"time"
"spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
"spark-server/internal/pkg/i18n"
"spark-server/internal/pkg/reqctx"
"spark-server/internal/pkg/validate"
)
// Profile is the public account view.
type Profile struct {
UID int64 `json:"uid"`
SparkID string `json:"spark_id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
QRCode string `json:"qrcode"`
Birthday string `json:"birthday"`
Gender entity.Gender `json:"gender"`
Status entity.Status `json:"status"`
LastLoginTime int64 `json:"last_login_time"`
LastActiveTime int64 `json:"last_active_time"`
CreateTime int64 `json:"create_time"`
UpdateTime int64 `json:"update_time"`
}
func PublicProfile(u *entity.User) *Profile {
if u == nil {
return nil
}
birthday := ""
if !u.Birthday.IsZero() {
birthday = u.Birthday.Format("2006-01-02")
}
return &Profile{
UID: u.ID,
SparkID: u.SparkID,
Nickname: u.Nickname,
Avatar: u.Avatar,
QRCode: u.QRCode,
Birthday: birthday,
Gender: u.Gender,
Status: u.Status,
LastLoginTime: unixMS(u.LastLoginAt),
LastActiveTime: unixMS(u.LastActiveAt),
CreateTime: unixMS(u.CreatedAt),
UpdateTime: unixMS(u.UpdatedAt),
}
}
func unixMS(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.UnixMilli()
}
func (s *Service) Get(ctx context.Context, id int64) (*entity.User, error) {
if s == nil || s.repo == nil || id <= 0 {
return nil, errs.ErrNotFound
}
return s.repo.GetByID(ctx, id)
}
func (s *Service) GetByQRCode(ctx context.Context, qrcode string) (*entity.User, error) {
if s == nil || s.repo == nil || !entity.ValidQRCode(qrcode) {
return nil, errs.ErrNotFound
}
return s.repo.GetByQRCode(ctx, qrcode)
}
type UpdateSparkIDInput struct {
SparkID string `json:"spark_id" validate:"required,min=8,max=16" label:"field.spark_id" errmsg:"required:validate.user.spark_id_required;min:validate.user.spark_id_invalid;max:validate.user.spark_id_invalid"`
}
// UpdateProfileInput is a partial profile PATCH. Nil fields are left unchanged.
type UpdateProfileInput struct {
SparkID *string `json:"spark_id" validate:"omitempty,min=8,max=16" label:"field.spark_id" errmsg:"min:validate.user.spark_id_invalid;max:validate.user.spark_id_invalid"`
Nickname *string `json:"nickname" validate:"omitempty,min=1,max=64" label:"field.nickname" errmsg:"min:validate.user.nickname_invalid;max:validate.user.nickname_invalid"`
Avatar *string `json:"avatar" validate:"omitempty,max=512" label:"field.avatar" errmsg:"max:validate.user.avatar_invalid"`
Gender *entity.Gender `json:"gender" validate:"omitempty,oneof=0 1 2" label:"field.gender" errmsg:"oneof:validate.user.gender_invalid"`
Birthday *string `json:"birthday" validate:"omitempty" label:"field.birthday"`
}
func (s *Service) UpdateSparkID(ctx context.Context, userID int64, sparkID string) (*entity.User, error) {
id := sparkID
return s.UpdateProfile(ctx, userID, UpdateProfileInput{SparkID: &id})
}
func (s *Service) UpdateProfile(ctx context.Context, userID int64, in UpdateProfileInput) (*entity.User, error) {
if s == nil || s.repo == nil || userID <= 0 {
return nil, errs.ErrUnauthorized
}
if err := validate.StructLang(reqctx.Lang(ctx), in); err != nil {
return nil, err
}
u, err := s.repo.GetByID(ctx, userID)
if err != nil {
return nil, err
}
if !profilePatchSet(&in) {
return u, nil
}
if in.Nickname != nil {
nick, err := s.gateText(ctx, userID, strings.TrimSpace(*in.Nickname))
if err != nil {
return nil, err
}
in.Nickname = &nick
}
if err := applyProfilePatch(ctx, u, &in); err != nil {
return nil, err
}
u.UpdatedAt = time.Now()
if err := s.repo.UpdateProfile(ctx, u); err != nil {
return nil, err
}
return u, nil
}
func profilePatchSet(in *UpdateProfileInput) bool {
if in == nil {
return false
}
return in.SparkID != nil || in.Nickname != nil || in.Avatar != nil || in.Gender != nil || in.Birthday != nil
}
func applyProfilePatch(ctx context.Context, u *entity.User, in *UpdateProfileInput) error {
if in == nil {
return nil
}
lang := reqctx.Lang(ctx)
if in.SparkID != nil {
sparkID := strings.TrimSpace(*in.SparkID)
if !entity.ValidSparkID(sparkID) {
return errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserSparkIDInvalid, nil))
}
u.SparkID = sparkID
}
if in.Nickname != nil {
nick := strings.TrimSpace(*in.Nickname)
if nick == "" || len(nick) > 64 {
return errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserNicknameInvalid, nil))
}
u.Nickname = nick
}
if in.Avatar != nil {
avatar := strings.TrimSpace(*in.Avatar)
if len(avatar) > 512 {
return errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserAvatarInvalid, nil))
}
u.Avatar = avatar
}
if in.Gender != nil {
if !in.Gender.Valid() {
return errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserGenderInvalid, nil))
}
u.Gender = *in.Gender
}
if in.Birthday != nil {
day, err := parseBirthday(lang, *in.Birthday)
if err != nil {
return err
}
u.Birthday = day
}
return nil
}
func parseBirthday(lang i18n.Lang, raw string) (time.Time, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return time.Time{}, nil
}
day, err := time.ParseInLocation("2006-01-02", raw, time.Local)
if err != nil {
return time.Time{}, errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserBirthdayInvalid, nil))
}
today := time.Now().In(time.Local)
if day.After(today) || day.Year() < 1900 {
return time.Time{}, errs.ErrInvalidArgument.WithMsg(i18n.T(lang, i18n.KeyUserBirthdayInvalid, nil))
}
return day, nil
}