182 lines
5.6 KiB
Go
182 lines
5.6 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=6,max=32" 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=6,max=32" 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 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
|
|
}
|