package useCases import ( "RenewCMS/internal/domain/article" "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/author" "RenewCMS/internal/domain/site" "context" "errors" ) type CreateArticleUseCase struct { articleRepository article.Repository bodyRenderer article.BodyRenderer recorder audit.Recorder sites site.Repository authors author.Repository } type CreateArticleCommand struct { Title string Body string Format article.BodyFormat CreatedBy uint32 } func NewCreateArticleUseCase(articleRepository article.Repository, bodyRenderer article.BodyRenderer, recorder audit.Recorder, sites site.Repository, authors author.Repository) *CreateArticleUseCase { return &CreateArticleUseCase{articleRepository: articleRepository, bodyRenderer: bodyRenderer, recorder: recorder, sites: sites, authors: authors} } func (g *CreateArticleUseCase) defaultSiteID() (uint32, error) { defaultSite, err := g.sites.GetDefault() if err != nil { return 0, err } return defaultSite.ID, nil } func (g *CreateArticleUseCase) creatorBylines(userID uint32) ([]author.Byline, error) { found, err := g.authors.GetByUserID(userID) if errors.Is(err, author.ErrAuthorNotFound) { return []author.Byline{}, nil } if err != nil { return nil, err } return []author.Byline{found.Byline()}, nil } func (g *CreateArticleUseCase) CreateArticle(ctx context.Context, createArticle CreateArticleCommand) (article.Article, error) { event := audit.NewEvent(audit.ActionArticleCreate, audit.CategoryArticles) if err := article.ValidateTitle(createArticle.Title); err != nil { recordFailure(ctx, g.recorder, event, err) return article.Article{}, err } siteID, err := g.defaultSiteID() if err != nil { recordFailure(ctx, g.recorder, event, err) return article.Article{}, err } creditedAuthors, err := g.creatorBylines(createArticle.CreatedBy) if err != nil { recordFailure(ctx, g.recorder, event, err) return article.Article{}, err } created, err := g.createWithFreeSlug(article.Article{ Title: createArticle.Title, Body: createArticle.Body, IsOnline: false, SiteID: siteID, Authors: creditedAuthors, SEODismissed: []string{}, }) if err != nil { recordFailure(ctx, g.recorder, event, err) return article.Article{}, err } event.Target = articleTarget(created) g.recorder.Record(ctx, event) return renderArticle(g.bodyRenderer, created, createArticle.Format) } func (g *CreateArticleUseCase) createWithFreeSlug(draft article.Article) (article.Article, error) { base := article.GenerateSlug(draft.Title) for attempt := 0; attempt < maxCreateSlugAttempts; attempt++ { slug, err := freeSlug(g.articleRepository, draft.SiteID, base, 0, firstSlugAttempt) if err != nil { return article.Article{}, err } draft.Slug = slug created, err := g.articleRepository.Create(draft) if !isSlugTakenError(err) { return created, err } } return article.Article{}, article.ErrSlugTaken } func articleTarget(a article.Article) audit.Target { return audit.NewTarget(audit.TargetArticle, a.ID, a.Title) }