feat: 分类系统 + 标签系统 — Phase 4 完成
分类:Model/Repo/Service/Controller + 管理后台树形页面 + 首页导航栏
标签:Model/Repo/Service/Controller + /tags/{slug} 落地页 + 写文章页输入
Post 加 CategoryID,Create/Update 支持分类和标签
SQL 迁移含 categories/tags/post_tags 表及预设未分类
This commit is contained in:
63
internal/repository/category_repo.go
Normal file
63
internal/repository/category_repo.go
Normal file
@ -0,0 +1,63 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"metazone.cc/mce/internal/model"
|
||||
"metazone.cc/mce/internal/service"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CategoryRepo 分类数据访问
|
||||
type CategoryRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewCategoryRepo 构造函数
|
||||
func NewCategoryRepo(db *gorm.DB) *CategoryRepo {
|
||||
return &CategoryRepo{db: db}
|
||||
}
|
||||
|
||||
// WithTx 基于事务创建新实例
|
||||
func (r *CategoryRepo) WithTx(tx *gorm.DB) service.CategoryStore {
|
||||
return &CategoryRepo{db: tx}
|
||||
}
|
||||
|
||||
// Create 创建分类
|
||||
func (r *CategoryRepo) Create(c *model.Category) error {
|
||||
return r.db.Create(c).Error
|
||||
}
|
||||
|
||||
// Update 更新分类
|
||||
func (r *CategoryRepo) Update(c *model.Category) error {
|
||||
return r.db.Save(c).Error
|
||||
}
|
||||
|
||||
// Delete 删除分类
|
||||
func (r *CategoryRepo) Delete(id uint) error {
|
||||
return r.db.Delete(&model.Category{}, id).Error
|
||||
}
|
||||
|
||||
// FindByID 按 ID 查询
|
||||
func (r *CategoryRepo) FindByID(id uint) (*model.Category, error) {
|
||||
var c model.Category
|
||||
err := r.db.First(&c, id).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// ListAll 查询全部分类(按 sort 排序)
|
||||
func (r *CategoryRepo) ListAll() ([]model.Category, error) {
|
||||
var list []model.Category
|
||||
err := r.db.Order("sort ASC, id ASC").Find(&list).Error
|
||||
return list, err
|
||||
}
|
||||
|
||||
// GetLeaves 查询所有叶子分类(有子分类的父节点不返回)
|
||||
func (r *CategoryRepo) GetLeaves() ([]model.Category, error) {
|
||||
var leaves []model.Category
|
||||
err := r.db.Where("id NOT IN (SELECT DISTINCT parent_id FROM categories WHERE parent_id IS NOT NULL)").
|
||||
Order("sort ASC, id ASC").Find(&leaves).Error
|
||||
return leaves, err
|
||||
}
|
||||
97
internal/repository/tag_repo.go
Normal file
97
internal/repository/tag_repo.go
Normal file
@ -0,0 +1,97 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"metazone.cc/mce/internal/model"
|
||||
"metazone.cc/mce/internal/service"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TagRepo 标签数据访问
|
||||
type TagRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTagRepo 构造函数
|
||||
func NewTagRepo(db *gorm.DB) *TagRepo {
|
||||
return &TagRepo{db: db}
|
||||
}
|
||||
|
||||
// WithTx 基于事务创建新实例
|
||||
func (r *TagRepo) WithTx(tx *gorm.DB) service.TagStore {
|
||||
return &TagRepo{db: tx}
|
||||
}
|
||||
|
||||
// FindOrCreate 查找或创建标签(按名字)
|
||||
func (r *TagRepo) FindOrCreate(name, slug string) (*model.Tag, error) {
|
||||
var tag model.Tag
|
||||
err := r.db.Where("slug = ?", slug).First(&tag).Error
|
||||
if err == nil {
|
||||
return &tag, nil
|
||||
}
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
tag = model.Tag{Name: name, Slug: slug}
|
||||
if err := r.db.Create(&tag).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// FindBySlug 按 slug 查询
|
||||
func (r *TagRepo) FindBySlug(slug string) (*model.Tag, error) {
|
||||
var tag model.Tag
|
||||
err := r.db.Where("slug = ?", slug).First(&tag).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tag, nil
|
||||
}
|
||||
|
||||
// SetPostTags 设置文章的标签(删除旧的,插入新的)
|
||||
func (r *TagRepo) SetPostTags(postID uint, tagIDs []uint) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("post_id = ?", postID).Delete(&model.PostTag{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tagID := range tagIDs {
|
||||
if err := tx.Create(&model.PostTag{PostID: postID, TagID: tagID}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetPostTags 获取文章关联的标签
|
||||
func (r *TagRepo) GetPostTags(postID uint) ([]model.Tag, error) {
|
||||
var tags []model.Tag
|
||||
err := r.db.Table("tags").
|
||||
Joins("JOIN post_tags ON post_tags.tag_id = tags.id").
|
||||
Where("post_tags.post_id = ?", postID).
|
||||
Find(&tags).Error
|
||||
return tags, err
|
||||
}
|
||||
|
||||
// GetPostsByTag 按标签查询文章(公开+已发布)
|
||||
func (r *TagRepo) GetPostsByTag(tagID uint, offset, limit int) ([]model.Post, int64, error) {
|
||||
var total int64
|
||||
query := r.db.Table("posts").
|
||||
Joins("JOIN post_tags ON post_tags.post_id = posts.id").
|
||||
Joins("LEFT JOIN users ON users.id = posts.user_id").
|
||||
Where("post_tags.tag_id = ?", tagID).
|
||||
Where("posts.status = ?", model.PostStatusApproved).
|
||||
Where("posts.visibility = ?", model.VisibilityPublic).
|
||||
Where("posts.deleted_at IS NULL").
|
||||
Select("posts.*, users.username as author_name")
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var posts []model.Post
|
||||
err := query.Order("CASE WHEN posts.pin_type = 'global' THEN 0 ELSE 1 END ASC, posts.pinned_at DESC NULLS LAST, posts.created_at DESC").
|
||||
Offset(offset).Limit(limit).Find(&posts).Error
|
||||
return posts, total, err
|
||||
}
|
||||
Reference in New Issue
Block a user