feat: 增加友链申请、独立页面、投票/悬赏/抽奖帖与侧栏签到,并统一开发数据目录

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-27 06:23:54 +08:00
parent df19752a1e
commit 2208af7070
80 changed files with 9620 additions and 641 deletions

176
service/site_page.go Normal file
View File

@@ -0,0 +1,176 @@
package service
import (
"errors"
"strings"
"git.iioio.com/freefire/jiang13-forum/model"
)
var (
ErrSitePageNotFound = errors.New("单页不存在")
ErrSitePageSlugUsed = errors.New("slug 已被占用")
)
// SitePageService 自定义单页
type SitePageService struct {
filter *SensitiveFilter
}
func NewSitePageService(filter *SensitiveFilter) *SitePageService {
return &SitePageService{filter: filter}
}
// SitePageSummary 公开列表摘要
type SitePageSummary struct {
ID uint `json:"id"`
Title string `json:"title"`
Slug string `json:"slug"`
ShowInFooter bool `json:"show_in_footer"`
ShowInNav bool `json:"show_in_nav"`
SortOrder int `json:"sort_order"`
}
func (s *SitePageService) ListPublished() ([]SitePageSummary, error) {
var rows []model.SitePage
err := model.DB.Where("published = ?", true).
Order("sort_order ASC, id ASC").
Find(&rows).Error
if err != nil {
return nil, err
}
out := make([]SitePageSummary, len(rows))
for i, p := range rows {
out[i] = SitePageSummary{
ID: p.ID, Title: p.Title, Slug: p.Slug,
ShowInFooter: p.ShowInFooter, ShowInNav: p.ShowInNav, SortOrder: p.SortOrder,
}
}
return out, nil
}
func (s *SitePageService) ListAll() ([]model.SitePage, error) {
var rows []model.SitePage
err := model.DB.Order("sort_order ASC, id ASC").Find(&rows).Error
if err != nil {
return nil, err
}
return rows, nil
}
func (s *SitePageService) GetBySlug(slug string, allowUnpublished bool) (*model.SitePage, error) {
slug, ok := NormalizePageSlug(slug)
if !ok {
return nil, ErrSitePageNotFound
}
var page model.SitePage
q := model.DB.Where("slug = ?", slug)
if !allowUnpublished {
q = q.Where("published = ?", true)
}
if err := q.First(&page).Error; err != nil {
return nil, ErrSitePageNotFound
}
page.Content = SanitizePostHTML(page.Content)
return &page, nil
}
func (s *SitePageService) GetByID(id uint) (*model.SitePage, error) {
var page model.SitePage
if err := model.DB.First(&page, id).Error; err != nil {
return nil, ErrSitePageNotFound
}
return &page, nil
}
type SitePageInput struct {
Title string `json:"title"`
Slug string `json:"slug"`
Content string `json:"content"`
Published bool `json:"published"`
SortOrder int `json:"sort_order"`
ShowInFooter bool `json:"show_in_footer"`
ShowInNav bool `json:"show_in_nav"`
}
func (s *SitePageService) Create(in SitePageInput) (*model.SitePage, error) {
page, err := s.normalizeInput(in)
if err != nil {
return nil, err
}
var exists int64
model.DB.Model(&model.SitePage{}).Where("slug = ?", page.Slug).Count(&exists)
if exists > 0 {
return nil, ErrSitePageSlugUsed
}
if err := model.DB.Create(page).Error; err != nil {
return nil, err
}
return page, nil
}
func (s *SitePageService) Update(id uint, in SitePageInput) error {
page, err := s.GetByID(id)
if err != nil {
return err
}
next, err := s.normalizeInput(in)
if err != nil {
return err
}
var exists int64
model.DB.Model(&model.SitePage{}).Where("slug = ? AND id <> ?", next.Slug, id).Count(&exists)
if exists > 0 {
return ErrSitePageSlugUsed
}
return model.DB.Model(page).Updates(map[string]interface{}{
"title": next.Title,
"slug": next.Slug,
"content": next.Content,
"published": next.Published,
"sort_order": next.SortOrder,
"show_in_footer": next.ShowInFooter,
"show_in_nav": next.ShowInNav,
}).Error
}
func (s *SitePageService) Delete(id uint) error {
res := model.DB.Delete(&model.SitePage{}, id)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return ErrSitePageNotFound
}
return nil
}
func (s *SitePageService) ListSitemap(limit int) ([]model.SitePage, error) {
if limit <= 0 {
limit = 500
}
var rows []model.SitePage
err := model.DB.Where("published = ?", true).
Order("updated_at DESC").Limit(limit).Find(&rows).Error
return rows, err
}
func (s *SitePageService) normalizeInput(in SitePageInput) (*model.SitePage, error) {
title := s.filter.Filter(strings.TrimSpace(in.Title))
slug, ok := NormalizePageSlug(in.Slug)
if !ok {
return nil, errors.New("slug 格式无效2-64 位小写字母、数字、连字符)")
}
content := s.filter.Filter(SanitizePostHTML(in.Content))
if title == "" {
return nil, errors.New("标题不能为空")
}
if content == "" {
return nil, errors.New("正文不能为空")
}
return &model.SitePage{
Title: title, Slug: slug, Content: content,
Published: in.Published, SortOrder: in.SortOrder,
ShowInFooter: in.ShowInFooter, ShowInNav: in.ShowInNav,
}, nil
}