mirror of https://github.com/go-sonic/sonic.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
16 KiB
Go
511 lines
16 KiB
Go
package impl
|
|
|
|
import (
|
|
"context"
|
|
"database/sql/driver"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/gen/field"
|
|
|
|
"github.com/go-sonic/sonic/consts"
|
|
"github.com/go-sonic/sonic/dal"
|
|
"github.com/go-sonic/sonic/log"
|
|
"github.com/go-sonic/sonic/model/entity"
|
|
"github.com/go-sonic/sonic/model/param"
|
|
"github.com/go-sonic/sonic/model/property"
|
|
"github.com/go-sonic/sonic/service"
|
|
"github.com/go-sonic/sonic/util"
|
|
"github.com/go-sonic/sonic/util/xerr"
|
|
)
|
|
|
|
type basePostServiceImpl struct {
|
|
OptionService service.OptionService
|
|
BaseCommentService service.BaseCommentService
|
|
CounterCache *util.CounterCache[int32]
|
|
}
|
|
|
|
func NewBasePostService(optionService service.OptionService, baseCommentService service.BaseCommentService) service.BasePostService {
|
|
counterCache := util.NewCounterCache(time.Second*5, nil, func(postID int32, count int64) {
|
|
ctx := context.Background()
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
_, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).UpdateSimple(postDAL.Visits.Add(count))
|
|
if err != nil {
|
|
log.CtxErrorf(ctx, "increase visit err postID=%v", postID)
|
|
}
|
|
})
|
|
b := &basePostServiceImpl{
|
|
CounterCache: counterCache,
|
|
OptionService: optionService,
|
|
BaseCommentService: baseCommentService,
|
|
}
|
|
return b
|
|
}
|
|
|
|
func (b basePostServiceImpl) GetByStatus(ctx context.Context, status []consts.PostStatus, postType consts.PostType, sort *param.Sort) ([]*entity.Post, error) {
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
postDo := postDAL.WithContext(ctx)
|
|
if err := BuildSort(sort, &postDAL, &postDo); err != nil {
|
|
return nil, err
|
|
}
|
|
statusAdapt := make([]driver.Valuer, len(status))
|
|
for i, status := range status {
|
|
statusAdapt[i] = status
|
|
}
|
|
posts, err := postDAL.WithContext(ctx).Where(postDAL.Status.In(statusAdapt...), postDAL.Type.Eq(postType)).Find()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) BuildFullPath(ctx context.Context, post *entity.Post) (string, error) {
|
|
if post.Type == consts.PostTypePost {
|
|
return b.buildPostFullPath(ctx, post)
|
|
}
|
|
return b.buildSheetFullPath(ctx, post)
|
|
}
|
|
|
|
func (b basePostServiceImpl) GetByPostIDs(ctx context.Context, postIDs []int32) (map[int32]*entity.Post, error) {
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
posts, err := postDAL.WithContext(ctx).Where(postDAL.ID.In(postIDs...)).Find()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
result := make(map[int32]*entity.Post)
|
|
for _, post := range posts {
|
|
result[post.ID] = post
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) GetBySlug(ctx context.Context, slug string) (*entity.Post, error) {
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
post, err := postDAL.WithContext(ctx).Where(postDAL.Slug.Eq(slug)).Take()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
return post, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) buildPostFullPath(ctx context.Context, post *entity.Post) (string, error) {
|
|
postPermaLinkType, err := b.OptionService.GetOrByDefaultWithErr(ctx, property.PostPermalinkType, property.PostPermalinkType.DefaultValue)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pathSuffix, err := b.OptionService.GetPathSuffix(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
archivePrefix, err := b.OptionService.GetArchivePrefix(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
month := post.CreateTime.Month()
|
|
monthStr := util.IfElse(month < 10, "0"+strconv.Itoa(int(month)), strconv.Itoa(int(month))).(string)
|
|
day := post.CreateTime.Day()
|
|
dayStr := util.IfElse(month < 10, "0"+strconv.Itoa(day), strconv.Itoa(day)).(string)
|
|
|
|
fullPath := strings.Builder{}
|
|
isEnabled, err := b.OptionService.IsEnabledAbsolutePath(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if isEnabled {
|
|
blogBaseURL, err := b.OptionService.GetBlogBaseURL(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fullPath.WriteString(blogBaseURL)
|
|
}
|
|
fullPath.WriteString("/")
|
|
switch consts.PostPermalinkType(postPermaLinkType.(string)) {
|
|
case consts.PostPermalinkTypeDefault:
|
|
fullPath.WriteString(archivePrefix)
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(post.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
case consts.PostPermalinkTypeDate:
|
|
fullPath.WriteString(strconv.Itoa(post.CreateTime.Year()))
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(monthStr)
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(post.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
case consts.PostPermalinkTypeDay:
|
|
fullPath.WriteString(strconv.Itoa(post.CreateTime.Year()))
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(monthStr)
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(dayStr)
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(post.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
case consts.PostPermalinkTypeYear:
|
|
fullPath.WriteString(strconv.Itoa(post.CreateTime.Year()))
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(post.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
case consts.PostPermalinkTypeIDSlug:
|
|
fullPath.WriteString(archivePrefix)
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(strconv.Itoa(int(post.ID)))
|
|
fullPath.WriteString(post.Slug)
|
|
case consts.PostPermalinkTypeID:
|
|
fullPath.WriteString("?p=")
|
|
fullPath.WriteString(strconv.Itoa(int(post.ID)))
|
|
}
|
|
return fullPath.String(), nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) buildSheetFullPath(ctx context.Context, sheet *entity.Post) (string, error) {
|
|
sheetPermaLinkType, err := b.OptionService.GetOrByDefaultWithErr(ctx, property.SheetPermalinkType, property.SheetPermalinkType.DefaultValue)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
pathSuffix, err := b.OptionService.GetPathSuffix(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sheetPrefix, err := b.OptionService.GetOrByDefaultWithErr(ctx, property.SheetPrefix, property.SheetPrefix.DefaultValue)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
fullPath := strings.Builder{}
|
|
isEnabled, err := b.OptionService.IsEnabledAbsolutePath(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if isEnabled {
|
|
blogBaseURL, err := b.OptionService.GetBlogBaseURL(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
fullPath.WriteString(blogBaseURL)
|
|
}
|
|
fullPath.WriteString("/")
|
|
switch consts.SheetPermaLinkType(sheetPermaLinkType.(string)) {
|
|
case consts.SheetPermaLinkTypeSecondary:
|
|
fullPath.WriteString(sheetPrefix.(string))
|
|
fullPath.WriteString("/")
|
|
fullPath.WriteString(sheet.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
case consts.SheetPermaLinkTypeRoot:
|
|
fullPath.WriteString(sheet.Slug)
|
|
fullPath.WriteString(pathSuffix)
|
|
}
|
|
return fullPath.String(), nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) GetByPostID(ctx context.Context, postID int32) (*entity.Post, error) {
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
post, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).First()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
return post, nil
|
|
}
|
|
|
|
var summaryPattern = regexp.MustCompile(`[\t\r\n]`)
|
|
|
|
func (b basePostServiceImpl) GenerateSummary(ctx context.Context, htmlContent string) string {
|
|
text := util.CleanHTMLTag(htmlContent)
|
|
text = summaryPattern.ReplaceAllString(text, "")
|
|
summaryLength := b.OptionService.GetPostSummaryLength(ctx)
|
|
end := summaryLength
|
|
textRune := []rune(text)
|
|
if len(textRune) < end {
|
|
end = len(textRune)
|
|
}
|
|
return string(textRune[:end])
|
|
}
|
|
|
|
func (b basePostServiceImpl) Delete(ctx context.Context, postID int32) error {
|
|
err := dal.GetQueryByCtx(ctx).Transaction(func(tx *dal.Query) error {
|
|
postDAL := tx.Post
|
|
postTagDAL := tx.PostTag
|
|
postCategoryDAL := tx.PostCategory
|
|
postMetaDAL := tx.Meta
|
|
postCommentDAL := tx.Comment
|
|
|
|
deleteResult, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if deleteResult.RowsAffected != 1 {
|
|
return xerr.NoType.New("").WithMsg("delete post failed")
|
|
}
|
|
_, err = postTagDAL.WithContext(ctx).Where(postTagDAL.PostID.Eq(postID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postCategoryDAL.WithContext(ctx).Where(postCategoryDAL.PostID.Eq(postID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postMetaDAL.WithContext(ctx).Where(postMetaDAL.PostID.Eq(postID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postCommentDAL.WithContext(ctx).Where(postCommentDAL.PostID.Eq(postID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (b basePostServiceImpl) UpdateStatus(ctx context.Context, postID int32, status consts.PostStatus) (*entity.Post, error) {
|
|
if postID < 0 || status < consts.PostStatusPublished || status > consts.PostStatusIntimate {
|
|
return nil, xerr.BadParam.New("").WithMsg("postID or status parameter error").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
post, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).First()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
updateResult, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).UpdateColumnSimple(postDAL.Status.Value(status))
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
if updateResult.RowsAffected != 1 {
|
|
return nil, xerr.NoType.New("update post status failed postID=%v", postID).WithMsg("update post status failed")
|
|
}
|
|
post.Status = status
|
|
return post, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) DeleteBatch(ctx context.Context, postIDs []int32) error {
|
|
err := dal.GetQueryByCtx(ctx).Transaction(func(tx *dal.Query) error {
|
|
postDAL := tx.Post
|
|
postTagDAL := tx.PostTag
|
|
postCategoryDAL := tx.PostCategory
|
|
postMetaDAL := tx.Meta
|
|
postCommentDAL := tx.Comment
|
|
|
|
deleteResult, err := postDAL.WithContext(ctx).Where(postDAL.ID.In(postIDs...)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if deleteResult.RowsAffected != 1 {
|
|
return xerr.NoType.New("").WithMsg("delete post failed")
|
|
}
|
|
_, err = postTagDAL.WithContext(ctx).Where(postTagDAL.PostID.In(postIDs...)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postCategoryDAL.WithContext(ctx).Where(postCategoryDAL.PostID.In(postIDs...)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postMetaDAL.WithContext(ctx).Where(postMetaDAL.PostID.In(postIDs...)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
_, err = postCommentDAL.WithContext(ctx).Where(postCommentDAL.PostID.In(postIDs...)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
return nil
|
|
})
|
|
return err
|
|
}
|
|
|
|
func (b basePostServiceImpl) CreateOrUpdate(ctx context.Context, post *entity.Post, categoryIDs, tagIDs []int32, metas []param.Meta) (*entity.Post, error) {
|
|
err := dal.GetQueryByCtx(ctx).Transaction(func(tx *dal.Query) error {
|
|
postDAL := tx.Post
|
|
postCategoryDAL := tx.PostCategory
|
|
postTagDAL := tx.PostTag
|
|
categoryDAL := tx.Category
|
|
tagDAL := tx.Tag
|
|
postMetaDAL := tx.Meta
|
|
|
|
// create post
|
|
if post.ID == 0 {
|
|
postCount, err := postDAL.WithContext(ctx).Select(field.Star).Omit(postDAL.UpdateTime).Where(postDAL.Slug.Eq(post.Slug)).Count()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if postCount > 0 {
|
|
return xerr.BadParam.New("").WithMsg("文章别名已存在(Article alias already exists)").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
status := post.Status
|
|
err = postDAL.WithContext(ctx).Create(post)
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
// 😅gorm not insert zero value: https://gorm.io/docs/create.html
|
|
if status == consts.PostStatusPublished {
|
|
_, err = postDAL.WithContext(ctx).Where(postDAL.ID.Eq(post.ID)).UpdateColumnSimple(postDAL.Status.Value(status))
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
post.Status = status
|
|
}
|
|
} else {
|
|
// update post
|
|
slugCount, err := postDAL.WithContext(ctx).Where(postDAL.Slug.Eq(post.Slug), postDAL.ID.Neq(post.ID)).Count()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if slugCount > 0 {
|
|
return xerr.BadParam.New("").WithMsg("文章别名已存在(Article alias already exists)").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
updateResult, err := postDAL.WithContext(ctx).Select(field.Star).Omit(postDAL.Likes, postDAL.Visits).Where(postDAL.ID.Eq(post.ID)).Updates(post)
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if updateResult.RowsAffected != 1 {
|
|
return xerr.NoType.New("").WithMsg("更新文章失败(update post failed)")
|
|
}
|
|
}
|
|
|
|
// create post_category
|
|
if post.ID > 0 {
|
|
_, err := postCategoryDAL.WithContext(ctx).Where(postCategoryDAL.PostID.Eq(post.ID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
}
|
|
if len(categoryIDs) > 0 {
|
|
categoryCount, err := categoryDAL.WithContext(ctx).Where(categoryDAL.ID.In(categoryIDs...)).Count()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if int(categoryCount) != len(categoryIDs) {
|
|
return xerr.BadParam.New("").WithMsg("category not exist").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
pcs := make([]*entity.PostCategory, 0, len(categoryIDs))
|
|
for _, categoryID := range categoryIDs {
|
|
pc := &entity.PostCategory{
|
|
CategoryID: categoryID,
|
|
PostID: post.ID,
|
|
}
|
|
pcs = append(pcs, pc)
|
|
}
|
|
err = postCategoryDAL.WithContext(ctx).Create(pcs...)
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
}
|
|
// create post_tag
|
|
if post.ID > 0 {
|
|
_, err := postTagDAL.WithContext(ctx).Where(postTagDAL.PostID.Eq(post.ID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
}
|
|
if len(tagIDs) > 0 {
|
|
tagCount, err := tagDAL.WithContext(ctx).Where(tagDAL.ID.In(tagIDs...)).Count()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if int(tagCount) != len(tagIDs) {
|
|
return xerr.BadParam.New("").WithMsg("tag not exist").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
pts := make([]*entity.PostTag, 0, len(tagIDs))
|
|
for _, tagID := range tagIDs {
|
|
pts = append(pts, &entity.PostTag{
|
|
PostID: post.ID,
|
|
TagID: tagID,
|
|
})
|
|
}
|
|
err = postTagDAL.WithContext(ctx).Create(pts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// create metas
|
|
if post.ID > 0 {
|
|
_, err := postMetaDAL.WithContext(ctx).Where(postMetaDAL.PostID.Eq(post.ID)).Delete()
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
}
|
|
if len(metas) > 0 {
|
|
pms := make([]*entity.Meta, 0, len(metas))
|
|
for _, meta := range metas {
|
|
pms = append(pms, &entity.Meta{
|
|
PostID: post.ID,
|
|
MetaValue: meta.Value,
|
|
MetaKey: meta.Key,
|
|
})
|
|
}
|
|
err := postMetaDAL.WithContext(ctx).Create(pms...)
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return post, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) UpdateStatusBatch(ctx context.Context, status consts.PostStatus, postIDs []int32) ([]*entity.Post, error) {
|
|
if status < consts.PostStatusPublished || status > consts.PostStatusIntimate {
|
|
return nil, xerr.BadParam.New("").WithMsg("postID or status parameter error").WithStatus(xerr.StatusBadRequest)
|
|
}
|
|
|
|
uniquePostIDMap := make(map[int32]struct{})
|
|
for _, postID := range postIDs {
|
|
uniquePostIDMap[postID] = struct{}{}
|
|
}
|
|
uniqueIDs := make([]int32, 0)
|
|
for postID := range uniquePostIDMap {
|
|
uniqueIDs = append(uniqueIDs, postID)
|
|
}
|
|
err := dal.GetQueryByCtx(ctx).Transaction(func(tx *dal.Query) error {
|
|
postDAL := tx.Post
|
|
updateResult, err := postDAL.WithContext(ctx).Where(postDAL.ID.In(uniqueIDs...)).UpdateColumnSimple(postDAL.Status.Value(status))
|
|
if err != nil {
|
|
return WrapDBErr(err)
|
|
}
|
|
if updateResult.RowsAffected != int64(len(uniqueIDs)) {
|
|
return xerr.NoType.New("").WithMsg("update post status failed")
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
posts, err := postDAL.WithContext(ctx).Where(postDAL.ID.In(uniqueIDs...)).Find()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
return posts, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) UpdateDraftContent(ctx context.Context, postID int32, content, originalContent string) (*entity.Post, error) {
|
|
postDAL := dal.GetQueryByCtx(ctx).Post
|
|
post, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).First()
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
if post.OriginalContent != content {
|
|
updateResult, err := postDAL.WithContext(ctx).Where(postDAL.ID.Eq(postID)).UpdateColumnSimple(postDAL.OriginalContent.Value(originalContent), postDAL.FormatContent.Value(content))
|
|
if err != nil {
|
|
return nil, WrapDBErr(err)
|
|
}
|
|
if updateResult.RowsAffected != 1 {
|
|
return nil, xerr.NoType.New("").WithMsg("update post content failed")
|
|
}
|
|
}
|
|
post.OriginalContent = content
|
|
return post, nil
|
|
}
|
|
|
|
func (b basePostServiceImpl) IncreaseVisit(ctx context.Context, postID int32) {
|
|
b.CounterCache.IncrBy(postID, 1)
|
|
}
|