diff --git a/cli/posts/factory.go b/cli/posts/factory.go index 822c4dbe..e4a1859f 100644 --- a/cli/posts/factory.go +++ b/cli/posts/factory.go @@ -23,19 +23,15 @@ type Handler struct { func MakeHandler(input *Input, client *pkg.Client, env *env.Environment) Handler { db := boost.MakeDbConnection(env) + tags := &repository.Tags{DB: db} + categories := &repository.Categories{DB: db} + return Handler{ Input: input, - Client: client, IsDebugging: false, - Posts: &repository.Posts{ - DB: db, - Categories: &repository.Categories{ - DB: db, - }, - }, - Users: &repository.Users{ - DB: db, - }, + Client: client, + Users: &repository.Users{DB: db}, + Posts: &repository.Posts{DB: db, Categories: categories, Tags: tags}, } } @@ -81,8 +77,7 @@ func (h Handler) RenderArticle(post *markdown.Post) { fmt.Printf("Author: %s\n", post.Author) fmt.Printf("Image URL: %s\n", post.ImageURL) fmt.Printf("Image Alt: %s\n", post.ImageAlt) - fmt.Printf("Category: %s\n", post.Category) - fmt.Printf("Category Slug: %s\n", post.CategorySlug) + fmt.Printf("Categories: %s\n", post.Categories) fmt.Printf("Tags Alt: %s\n", post.Tags) fmt.Println("\n--- Content ---") fmt.Println(post.Content) diff --git a/cli/posts/handler.go b/cli/posts/handler.go index f3767fee..fcf01b7c 100644 --- a/cli/posts/handler.go +++ b/cli/posts/handler.go @@ -5,6 +5,7 @@ import ( "github.com/oullin/database" "github.com/oullin/pkg/cli" "github.com/oullin/pkg/markdown" + "strings" "time" ) @@ -24,6 +25,11 @@ func (h Handler) HandlePost(payload *markdown.Post) error { return fmt.Errorf("handler: the given published_at [%s] date is invalid", payload.PublishedAt) } + categories := h.ParseCategories(payload) + if len(categories) == 0 { + return fmt.Errorf("handler: the given categories [%v] are empty", payload.Categories) + } + attrs := database.PostsAttrs{ AuthorID: author.ID, PublishedAt: publishedAt, @@ -32,7 +38,7 @@ func (h Handler) HandlePost(payload *markdown.Post) error { Excerpt: payload.Excerpt, Content: payload.Content, ImageURL: payload.ImageURL, - Categories: h.ParseCategories(payload), + Categories: categories, Tags: h.ParseTags(payload), } @@ -47,25 +53,38 @@ func (h Handler) HandlePost(payload *markdown.Post) error { func (h Handler) ParseCategories(payload *markdown.Post) []database.CategoriesAttrs { var categories []database.CategoriesAttrs + parts := strings.Split(payload.Categories, ",") - slice := append(categories, database.CategoriesAttrs{ - Slug: payload.CategorySlug, - Name: payload.Category, - Description: "", - }) + for _, category := range parts { + slug := strings.TrimSpace(strings.ToLower(category)) - return slice + if item := h.Posts.FindCategoryBy(slug); item != nil { + categories = append(categories, database.CategoriesAttrs{ + Slug: item.Slug, + Name: item.Name, + Id: item.ID, + Description: item.Description, + }) + } + } + + return categories } func (h Handler) ParseTags(payload *markdown.Post) []database.TagAttrs { - var slice []database.TagAttrs + var tags []database.TagAttrs for _, tag := range payload.Tags { - slice = append(slice, database.TagAttrs{ - Slug: tag, - Name: tag, - }) + slug := strings.TrimSpace(strings.ToLower(tag)) + + if item := h.Posts.FindTagBy(slug); item != nil { + tags = append(tags, database.TagAttrs{ + Id: item.ID, + Slug: slug, + Name: slug, + }) + } } - return slice + return tags } diff --git a/database/attrs.go b/database/attrs.go index 8dc884fa..d905a42f 100644 --- a/database/attrs.go +++ b/database/attrs.go @@ -11,12 +11,14 @@ type UsersAttrs struct { } type CategoriesAttrs struct { + Id uint64 Slug string Name string Description string } type TagAttrs struct { + Id uint64 Slug string Name string } diff --git a/database/repository/categories.go b/database/repository/categories.go index 75490b52..53739b1f 100644 --- a/database/repository/categories.go +++ b/database/repository/categories.go @@ -4,14 +4,12 @@ import ( "fmt" "github.com/google/uuid" "github.com/oullin/database" - "github.com/oullin/env" "github.com/oullin/pkg/gorm" "strings" ) type Categories struct { - DB *database.Connection - Env *env.Environment + DB *database.Connection } func (c Categories) FindBy(slug string) *database.Category { diff --git a/database/repository/posts.go b/database/repository/posts.go index d05ea95c..12c2ef0c 100644 --- a/database/repository/posts.go +++ b/database/repository/posts.go @@ -4,15 +4,27 @@ import ( "fmt" "github.com/google/uuid" "github.com/oullin/database" - "github.com/oullin/env" "github.com/oullin/pkg/gorm" - baseGorm "gorm.io/gorm" ) type Posts struct { DB *database.Connection - Env *env.Environment Categories *Categories + Tags *Tags +} + +func (p Posts) FindCategoryBy(slug string) *database.Category { + return p.Categories.FindBy(slug) +} + +func (p Posts) FindTagBy(slug string) *database.Tag { + tag, err := p.Tags.FindOrCreate(slug) + + if err != nil { + return nil + } + + return tag } func (p Posts) Create(attrs database.PostsAttrs) (*database.Post, error) { @@ -27,24 +39,47 @@ func (p Posts) Create(attrs database.PostsAttrs) (*database.Post, error) { PublishedAt: attrs.PublishedAt, } - err := p.DB.Transaction(func(db *baseGorm.DB) error { - // --- Post. - if result := db.Create(&post); gorm.HasDbIssues(result.Error) { - return fmt.Errorf("issue creating posts: %s", result.Error) + if result := p.DB.Sql().Create(&post); gorm.HasDbIssues(result.Error) { + return nil, fmt.Errorf("issue creating posts: %s", result.Error) + } + + if err := p.LinkCategories(post, attrs.Categories); err != nil { + return nil, fmt.Errorf("issue creating the given post [%s] category: %s", attrs.Slug, err.Error()) + } + + if err := p.LinkTags(post, attrs.Tags); err != nil { + return nil, fmt.Errorf("issue creating the given post [%s] tags: %s", attrs.Slug, err.Error()) + } + + return &post, nil +} + +func (p Posts) LinkCategories(post database.Post, categories []database.CategoriesAttrs) error { + for _, category := range categories { + trace := database.PostCategory{ + CategoryID: category.Id, + PostID: post.ID, } - // --- Categories. - if _, err := p.Categories.CreateOrUpdate(post, attrs); err != nil { - return fmt.Errorf("issue creating the given post [%s] category: %s", attrs.Slug, err.Error()) + if result := p.DB.Sql().Create(&trace); gorm.HasDbIssues(result.Error) { + return fmt.Errorf("error linking categories [%s:%s]: %s", category.Name, post.Title, result.Error) } + } - // --- Returning [nil] commits the whole transaction. - return nil - }) + return nil +} - if err != nil { - return nil, fmt.Errorf("error creating posts [%s]: %s", attrs.Title, err.Error()) +func (p Posts) LinkTags(post database.Post, tags []database.TagAttrs) error { + for _, tag := range tags { + trace := database.PostTag{ + TagID: tag.Id, + PostID: post.ID, + } + + if result := p.DB.Sql().Create(&trace); gorm.HasDbIssues(result.Error) { + return fmt.Errorf("error linking tags [%s:%s]: %s", tag.Name, post.Title, result.Error) + } } - return &post, nil + return nil } diff --git a/database/repository/tags.go b/database/repository/tags.go new file mode 100644 index 00000000..eba250f7 --- /dev/null +++ b/database/repository/tags.go @@ -0,0 +1,53 @@ +package repository + +import ( + "fmt" + "github.com/google/uuid" + "github.com/oullin/database" + "github.com/oullin/pkg/gorm" + "golang.org/x/text/cases" + "golang.org/x/text/language" + "strings" +) + +type Tags struct { + DB *database.Connection +} + +func (t Tags) FindOrCreate(slug string) (*database.Tag, error) { + if item := t.FindBy(slug); item != nil { + return item, nil + } + + caser := cases.Title(language.English, cases.NoLower) + + tag := database.Tag{ + UUID: uuid.NewString(), + Slug: slug, + Name: caser.String(strings.ToLower(slug)), + } + + if result := t.DB.Sql().Save(&tag); gorm.HasDbIssues(result.Error) { + return nil, fmt.Errorf("error creating tag [%s]: %s", slug, result.Error) + } + + return &tag, nil +} + +func (t Tags) FindBy(slug string) *database.Tag { + tag := database.Tag{} + + result := t.DB.Sql(). + Where("LOWER(slug) = ?", strings.ToLower(slug)). + First(&tag) + + if gorm.HasDbIssues(result.Error) { + return nil + } + + if strings.Trim(tag.UUID, " ") != "" { + return &tag + } + + return nil +} diff --git a/pkg/markdown/handler.go b/pkg/markdown/handler.go index 29ef074c..1a488ef2 100644 --- a/pkg/markdown/handler.go +++ b/pkg/markdown/handler.go @@ -85,25 +85,5 @@ func Parse(data string) (*Post, error) { post.Content = body } - parseCategory(&post) - return &post, nil } - -func parseCategory(post *Post) { - category := post.FrontMatter.Category - parts := strings.Split(category, ":") - - post.Category = parts[1] - post.CategorySlug = parts[0] - - if len(parts) >= 2 { - post.Category = parts[1] - post.CategorySlug = parts[0] - - return - } - - post.Category = category - post.Slug = strings.ToLower(category) -} diff --git a/pkg/markdown/schema.go b/pkg/markdown/schema.go index 784b56c2..2d14f39e 100644 --- a/pkg/markdown/schema.go +++ b/pkg/markdown/schema.go @@ -11,17 +11,16 @@ type FrontMatter struct { Excerpt string `yaml:"excerpt"` Slug string `yaml:"slug"` Author string `yaml:"author"` - Category string `yaml:"category"` + Categories string `yaml:"categories"` PublishedAt string `yaml:"published_at"` Tags []string `yaml:"tags"` } type Post struct { FrontMatter - ImageURL string - ImageAlt string - Content string - CategorySlug string + ImageURL string + ImageAlt string + Content string } type Parser struct {