每账号仅一条 self(migration 40902);合盘只选 TA;账号昵称可改;首页穿衣/颜色/养生贴士。 Co-authored-by: Cursor <cursoragent@cursor.com>
151 lines
3.8 KiB
Go
151 lines
3.8 KiB
Go
package profile
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/model"
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
|
|
"github.com/yuxingu/digital-psychology/apps/api/internal/service/bootstrap"
|
|
)
|
|
|
|
// Service manages personal archives.
|
|
type Service struct {
|
|
Repo *repository.ProfileRepo
|
|
Reports *repository.ReportRepo
|
|
Bootstrap *bootstrap.Service
|
|
}
|
|
|
|
// CreateInput is validated create payload.
|
|
type CreateInput struct {
|
|
Relation string
|
|
DisplayName string
|
|
BirthDate time.Time
|
|
RelationType *string
|
|
BirthTime *string
|
|
BirthPlace *string
|
|
}
|
|
|
|
// ErrSelfExists is returned when creating a second self profile.
|
|
var ErrSelfExists = errors.New("self profile already exists")
|
|
|
|
// Create stores a profile for the user.
|
|
func (s *Service) Create(ctx context.Context, userID uuid.UUID, in CreateInput) (*model.Profile, error) {
|
|
if in.Relation != "self" && in.Relation != "other" {
|
|
return nil, errors.New("relation must be self or other")
|
|
}
|
|
if in.BirthDate.IsZero() {
|
|
return nil, errors.New("birth_date required")
|
|
}
|
|
if in.Relation == "self" {
|
|
n, err := s.Repo.CountActiveSelf(ctx, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n > 0 {
|
|
return nil, ErrSelfExists
|
|
}
|
|
}
|
|
name := in.DisplayName
|
|
if name == "" {
|
|
if in.Relation == "self" {
|
|
name = "我"
|
|
} else {
|
|
name = "TA"
|
|
}
|
|
}
|
|
p, err := s.Repo.Create(ctx, userID, in.Relation, name, in.BirthDate, in.RelationType, in.BirthTime, in.BirthPlace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.Bootstrap != nil {
|
|
s.Bootstrap.GenerateForProfile(ctx, userID, p)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// List returns user's profiles.
|
|
func (s *Service) List(ctx context.Context, userID uuid.UUID) ([]model.Profile, error) {
|
|
return s.Repo.ListByUser(ctx, userID)
|
|
}
|
|
|
|
// UpdateInput for PATCH /profiles/:id.
|
|
type UpdateInput struct {
|
|
DisplayName string
|
|
BirthDate time.Time
|
|
RelationType *string
|
|
BirthTime *string
|
|
BirthPlace *string
|
|
GeoLat *float64
|
|
GeoLng *float64
|
|
GeoVisible *bool
|
|
}
|
|
|
|
// Update patches an owned profile.
|
|
func (s *Service) Update(ctx context.Context, userID, profileID uuid.UUID, in UpdateInput) (*model.Profile, error) {
|
|
cur, err := s.Repo.GetForUser(ctx, userID, profileID)
|
|
if err != nil {
|
|
return nil, errors.New("profile not found")
|
|
}
|
|
name := strings.TrimSpace(in.DisplayName)
|
|
if name == "" {
|
|
name = cur.DisplayName
|
|
}
|
|
birth := in.BirthDate
|
|
if birth.IsZero() {
|
|
birth = cur.BirthDate
|
|
}
|
|
rt := in.RelationType
|
|
if rt == nil {
|
|
rt = cur.RelationType
|
|
}
|
|
bt := in.BirthTime
|
|
if bt == nil {
|
|
bt = cur.BirthTime
|
|
}
|
|
bp := in.BirthPlace
|
|
if bp == nil {
|
|
bp = cur.BirthPlace
|
|
}
|
|
birthChanged := !in.BirthDate.IsZero() && !sameDay(in.BirthDate, cur.BirthDate)
|
|
timeChanged := in.BirthTime != nil && strPtr(in.BirthTime) != strPtr(cur.BirthTime)
|
|
placeChanged := in.BirthPlace != nil && strPtr(in.BirthPlace) != strPtr(cur.BirthPlace)
|
|
|
|
p, err := s.Repo.UpdateForUser(ctx, userID, profileID, name, birth, rt, bt, bp, in.GeoLat, in.GeoLng, in.GeoVisible)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.Bootstrap != nil && (birthChanged || timeChanged || placeChanged) {
|
|
s.Bootstrap.GenerateForProfile(ctx, userID, p)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func sameDay(a, b time.Time) bool {
|
|
ay, am, ad := a.Date()
|
|
by, bm, bd := b.Date()
|
|
return ay == by && am == bm && ad == bd
|
|
}
|
|
|
|
func strPtr(p *string) string {
|
|
if p == nil {
|
|
return ""
|
|
}
|
|
return *p
|
|
}
|
|
|
|
// Delete soft-deletes an owned profile.
|
|
func (s *Service) Delete(ctx context.Context, userID, profileID uuid.UUID) error {
|
|
if _, err := s.Repo.GetForUser(ctx, userID, profileID); err != nil {
|
|
return errors.New("profile not found")
|
|
}
|
|
if s.Reports != nil {
|
|
_ = s.Reports.SoftDeleteForProfile(ctx, userID, profileID)
|
|
}
|
|
return s.Repo.SoftDeleteForUser(ctx, userID, profileID)
|
|
}
|