package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/author" "RenewCMS/internal/domain/user" "context" "errors" "strings" ) const maxOwnSlugAttempts = 50 type CreateMyAuthorUseCase struct { authors author.Repository users user.Repository recorder audit.Recorder } func NewCreateMyAuthorUseCase(authors author.Repository, users user.Repository, recorder audit.Recorder) *CreateMyAuthorUseCase { return &CreateMyAuthorUseCase{authors: authors, users: users, recorder: recorder} } func (u *CreateMyAuthorUseCase) Create(ctx context.Context, userID uint32) (author.Author, error) { caller, err := u.users.Get(userID) if err != nil { return author.Author{}, err } name := authorNameOf(caller) event := audit.NewEvent(audit.ActionAuthorCreate, audit.CategoryAuthors) event.Target = audit.Target{Type: audit.TargetAuthor, Name: name} created, err := u.create(caller, name) if err != nil { recordFailure(ctx, u.recorder, event, err) return author.Author{}, err } event.Target = authorTarget(created) u.recorder.Record(ctx, event) return created, nil } func authorNameOf(caller user.User) string { name := strings.TrimSpace(caller.DisplayName) if name == "" { name = strings.TrimSpace(caller.Username) } runes := []rune(name) return string(runes[:min(len(runes), author.MaxNameRunes)]) } func (u *CreateMyAuthorUseCase) create(caller user.User, name string) (author.Author, error) { _, err := u.authors.GetByUserID(caller.ID) if err == nil { return author.Author{}, author.ErrUserAlreadyLinked } if !errors.Is(err, author.ErrAuthorNotFound) { return author.Author{}, err } return u.createWithFreeSlug(caller.ID, name) } func (u *CreateMyAuthorUseCase) createWithFreeSlug(userID uint32, name string) (author.Author, error) { base := author.GenerateSlug(name) for attempt := 1; attempt <= maxOwnSlugAttempts; attempt++ { slug := author.SlugWithSuffix(base, attempt) taken, err := isSlugTaken(u.authors, slug) if err != nil { return author.Author{}, err } if taken { continue } created, err := u.authors.Create(author.Author{Name: name, Slug: slug, SocialLinks: []author.SocialLink{}, UserID: &userID, IsLinked: true}) if errors.Is(err, author.ErrSlugTaken) { continue } return created, err } return author.Author{}, author.ErrSlugTaken }