feat(15-01): add journal categories, tags, settings, and author slug

Squash the remaining golem15_journal_* tables plus the backend_users
author slug so a host migrate matches the frozen PHP schema.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jakub Zych
2026-10-06 18:28:17 +02:00
parent 445022ef71
commit 9ae4b9141e
11 changed files with 420 additions and 0 deletions

36
models/category.go Normal file
View File

@@ -0,0 +1,36 @@
package models
import "time"
// Category is a journal category stored on golem15_journal_categories.
type Category struct {
ID uint `gorm:"column:id;primaryKey"`
Name string `gorm:"column:name"`
Slug string `gorm:"column:slug"`
Code *string `gorm:"column:code"`
Description *string `gorm:"column:description"`
ParentID *uint `gorm:"column:parent_id"`
NestLeft *int `gorm:"column:nest_left"`
NestRight *int `gorm:"column:nest_right"`
NestDepth *int `gorm:"column:nest_depth"`
CreatedAt *time.Time `gorm:"column:created_at"`
UpdatedAt *time.Time `gorm:"column:updated_at"`
}
func (Category) TableName() string { return "golem15_journal_categories" }
// MorphName is the PHP class string stored on translate attributes.
func (Category) MorphName() string { return `Golem15\Journal\Models\Category` }
func (Category) Translatable() []string { return []string{"name", "description", "slug"} }
func (Category) TranslatableIndexes() []string { return []string{"slug"} }
// Fillable is JOURNAL-002: nest_* stay out of mass assignment.
func (Category) Fillable() []string {
return []string{"name", "slug", "code", "description", "parent_id"}
}
func init() {
Register(Category{})
}

View File

@@ -2,6 +2,9 @@ package models
import (
"time"
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
)
// Post is a journal entry stored on golem15_journal_posts.
@@ -21,6 +24,10 @@ type Post struct {
Sources *string `gorm:"column:sources;type:jsonb"`
CreatedAt *time.Time `gorm:"column:created_at"`
UpdatedAt *time.Time `gorm:"column:updated_at"`
User *cabana.BackendUser `gorm:"foreignKey:UserID" json:"-"`
Categories []Category `gorm:"many2many:golem15_journal_posts_categories;" json:"-"`
Tags []Tag `gorm:"many2many:golem15_journal_posts_tags;" json:"-"`
}
func (Post) TableName() string { return "golem15_journal_posts" }
@@ -43,6 +50,13 @@ func (Post) Fillable() []string {
}
}
func (Post) AttachRelations() []attach.Relation {
return []attach.Relation{
{Name: "featured_images", Many: true, Public: true},
{Name: "content_images", Many: true, Public: true},
}
}
func init() {
Register(Post{})
}

54
models/settings.go Normal file
View File

@@ -0,0 +1,54 @@
package models
// Settings is the singleton journal settings row (ID=1) on golem15_journal_settings.
type Settings struct {
ID uint `gorm:"column:id;primaryKey"`
ShowAllPosts bool `gorm:"column:show_all_posts"`
UseRichEditor bool `gorm:"column:use_rich_editor"`
SearchUseTypesense bool `gorm:"column:search_use_typesense"`
SearchTitleWeight int `gorm:"column:search_title_weight"`
SearchExcerptWeight int `gorm:"column:search_excerpt_weight"`
SearchContentWeight int `gorm:"column:search_content_weight"`
RSSEnabled bool `gorm:"column:rss_enabled"`
RSSIncludeContent bool `gorm:"column:rss_include_content"`
RSSTitle *string `gorm:"column:rss_title"`
RSSDescription *string `gorm:"column:rss_description"`
RSSLanguage string `gorm:"column:rss_language"`
RSSCopyright *string `gorm:"column:rss_copyright"`
RSSImageURL *string `gorm:"column:rss_image_url"`
RSSPostsPerFeed int `gorm:"column:rss_posts_per_feed"`
}
func (Settings) TableName() string { return "golem15_journal_settings" }
func (Settings) Fillable() []string {
return []string{
"show_all_posts", "use_rich_editor", "search_use_typesense",
"search_title_weight", "search_excerpt_weight", "search_content_weight",
"rss_enabled", "rss_include_content", "rss_title", "rss_description",
"rss_language", "rss_copyright", "rss_image_url", "rss_posts_per_feed",
}
}
func (Settings) Rules() map[string]string {
return map[string]string{
"show_all_posts": "boolean",
"use_rich_editor": "boolean",
"search_use_typesense": "boolean",
"search_title_weight": "nullable|integer|min:0|max:100",
"search_excerpt_weight": "nullable|integer|min:0|max:100",
"search_content_weight": "nullable|integer|min:0|max:100",
"rss_enabled": "boolean",
"rss_include_content": "boolean",
"rss_title": "nullable|string|max:255",
"rss_description": "nullable|string|max:1000",
"rss_language": "nullable|string|max:10",
"rss_copyright": "nullable|string|max:255",
"rss_image_url": "nullable|url|max:500",
"rss_posts_per_feed": "nullable|integer|min:1|max:100",
}
}
func init() {
Register(Settings{})
}

26
models/tag.go Normal file
View File

@@ -0,0 +1,26 @@
package models
import "time"
// Tag is a journal tag stored on golem15_journal_tags. It is not translatable.
type Tag struct {
ID uint `gorm:"column:id;primaryKey"`
Name string `gorm:"column:name"`
Slug string `gorm:"column:slug"`
Description *string `gorm:"column:description"`
CreatedAt *time.Time `gorm:"column:created_at"`
UpdatedAt *time.Time `gorm:"column:updated_at"`
}
func (Tag) TableName() string { return "golem15_journal_tags" }
func (Tag) MorphName() string { return `Golem15\Journal\Models\Tag` }
// Fillable is JOURNAL-001.
func (Tag) Fillable() []string {
return []string{"name", "slug", "description"}
}
func init() {
Register(Tag{})
}