package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/author" "RenewCMS/internal/domain/image" "RenewCMS/internal/domain/user" "context" "strings" ) type UpdateAuthorUseCase struct { authors author.Repository users user.Repository images image.Repository recorder audit.Recorder } func NewUpdateAuthorUseCase(authors author.Repository, users user.Repository, images image.Repository, recorder audit.Recorder) *UpdateAuthorUseCase { return &UpdateAuthorUseCase{authors: authors, users: users, images: images, recorder: recorder} } func (u *UpdateAuthorUseCase) Update(ctx context.Context, id uint32, caller AuthorCaller, patch author.Patch) (author.Author, error) { before, err := u.authors.Get(id) if err != nil { return author.Author{}, err } event := audit.NewEvent(audit.ActionAuthorEdit, audit.CategoryAuthors) event.Target = authorTarget(before) after, err := u.prepare(before, caller, patch) if err != nil { recordFailure(ctx, u.recorder, event, err) return author.Author{}, err } changes := authorChanges(u.users, u.images, before, after) if len(changes) == 0 { return u.present(before, caller), nil } saved, err := u.authors.Update(after) if err != nil { recordFailure(ctx, u.recorder, event, err) return author.Author{}, err } event.Target = authorTarget(saved) event.Changes = changes u.recorder.Record(ctx, event) return u.present(saved, caller), nil } func (u *UpdateAuthorUseCase) present(a author.Author, caller AuthorCaller) author.Author { if caller.isAdmin() { return withUserEmail(u.users, a) } a.UserEmail = nil return a } func (u *UpdateAuthorUseCase) prepare(before author.Author, caller AuthorCaller, patch author.Patch) (author.Author, error) { if err := authorizeEdit(before, caller, patch); err != nil { return author.Author{}, err } after := before if patch.Name != nil { after.Name = strings.TrimSpace(*patch.Name) } if patch.Bio != nil { after.Bio = *patch.Bio } if patch.Website.Set { after.Website = patch.Website.Value } if patch.SocialLinks != nil { after.SocialLinks = *patch.SocialLinks } website, err := validateAuthorContent(after.Bio, after.Website, after.SocialLinks) if err != nil { return author.Author{}, err } after.Website = website if err := u.applySlug(&after, patch); err != nil { return author.Author{}, err } if err := u.applyAvatar(&after, patch); err != nil { return author.Author{}, err } return after, u.applyLink(&after, patch) } func authorizeEdit(before author.Author, caller AuthorCaller, patch author.Patch) error { if caller.isAdmin() { return nil } if before.UserID == nil || *before.UserID != caller.UserID || patch.UserID.Set { return author.ErrAuthorRights } return nil } func (u *UpdateAuthorUseCase) applySlug(after *author.Author, patch author.Patch) error { if patch.Slug == nil || *patch.Slug == after.Slug { return nil } if !author.IsValidSlug(*patch.Slug) { return author.ErrInvalidSlug } taken, err := isSlugTaken(u.authors, *patch.Slug) if err != nil { return err } if taken { return author.ErrSlugTaken } after.Slug = *patch.Slug return nil } func (u *UpdateAuthorUseCase) applyAvatar(after *author.Author, patch author.Patch) error { if !patch.AvatarID.Set { return nil } if err := ensureImageExists(u.images, patch.AvatarID.Value); err != nil { return err } after.AvatarID = patch.AvatarID.Value return nil } func (u *UpdateAuthorUseCase) applyLink(after *author.Author, patch author.Patch) error { if !patch.UserID.Set { return nil } requested := patch.UserID.Value if requested == nil { after.UserID = nil after.IsLinked = false return nil } if after.UserID != nil { if *after.UserID == *requested { return nil } return author.ErrAuthorAlreadyLinked } if err := ensureUserCanBeLinked(u.users, u.authors, *requested); err != nil { return err } after.UserID = requested after.IsLinked = true return nil }