You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

338 lines
9.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package story
import (
"database/sql"
"fmt"
"net/http"
"sort"
"strings"
"time"
"unicode/utf8"
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
"github.com/imdotdev/im.dev/server/internal/tags"
"github.com/imdotdev/im.dev/server/internal/user"
"github.com/imdotdev/im.dev/server/pkg/config"
"github.com/imdotdev/im.dev/server/pkg/db"
"github.com/imdotdev/im.dev/server/pkg/e"
"github.com/imdotdev/im.dev/server/pkg/models"
"github.com/imdotdev/im.dev/server/pkg/utils"
)
func UserPosts(uid int64) (models.Posts, *e.Error) {
ars := make(models.Posts, 0)
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,created,updated from posts where creator=?", uid)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return ars, e.New(http.StatusInternalServerError, e.Internal)
}
creator := &models.UserSimple{ID: uid}
creator.Query()
for rows.Next() {
ar := &models.Post{}
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.Created, &ar.Updated)
if err != nil {
logger.Warn("scan post error", "error", err)
continue
}
ar.Creator = creator
ar.Comments = GetCommentCount(ar.ID)
ars = append(ars, ar)
}
sort.Sort(ars)
return ars, nil
}
func TagPosts(tagID int64) (models.Posts, *e.Error) {
ars := make(models.Posts, 0)
// get post ids
rows, err := db.Conn.Query("select post_id from tag_post where tag_id=?", tagID)
if err != nil {
logger.Warn("get user posts error", "error", err)
return ars, e.New(http.StatusInternalServerError, e.Internal)
}
postIDs := make([]string, 0)
for rows.Next() {
var id string
rows.Scan(&id)
postIDs = append(postIDs, id)
}
ids := strings.Join(postIDs, "','")
q := fmt.Sprintf("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts where id in ('%s')", ids)
rows, err = db.Conn.Query(q)
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return ars, e.New(http.StatusInternalServerError, e.Internal)
}
for rows.Next() {
ar := &models.Post{}
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.CreatorID, &ar.Created, &ar.Updated)
if err != nil {
logger.Warn("scan post error", "error", err)
continue
}
creator := &models.UserSimple{ID: ar.CreatorID}
creator.Query()
ar.Creator = creator
ar.Comments = GetCommentCount(ar.ID)
ars = append(ars, ar)
}
sort.Sort(ars)
return ars, nil
}
func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
user := user.CurrentUser(c)
post := &models.Post{}
err := c.Bind(&post)
if err != nil {
return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
}
if strings.TrimSpace(post.Title) == "" || utf8.RuneCountInString(post.Brief) > config.Data.Posts.BriefMaxLen {
return nil, e.New(http.StatusBadRequest, "标题格式不合法")
}
if strings.TrimSpace(post.URL) != "" && !govalidator.IsURL(post.URL) {
return nil, e.New(http.StatusBadRequest, "URL格式不正确")
}
if strings.TrimSpace(post.Cover) != "" && !govalidator.IsURL(post.Cover) {
return nil, e.New(http.StatusBadRequest, "图片链接格式不正确")
}
isExternal := true
if strings.TrimSpace(post.URL) == "" {
isExternal = false
}
if isExternal {
// internal post, need creator role
if !user.Role.IsCreator() {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
} else {
// external post, need editor role
if !user.Role.IsEditor() {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
if len(post.Md) <= config.Data.Posts.BriefMaxLen {
post.Brief = post.Md
} else {
post.Brief = string([]rune(post.Md)[:config.Data.Posts.BriefMaxLen])
}
}
now := time.Now()
md := utils.Compress(post.Md)
setSlug(user.ID, post)
if post.ID == "" {
post.ID = utils.GenStoryID(models.StoryPost)
//create
_, err := db.Conn.Exec("INSERT INTO posts (id,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
post.ID, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusPublished, now, now)
if err != nil {
logger.Warn("submit post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
} else {
// 只有创建者自己才能更新内容
creator, _ := GetPostCreator(post.ID)
if creator != user.ID {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
_, err = db.Conn.Exec("UPDATE posts SET slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
if err != nil {
logger.Warn("upate post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
}
//update tags
_, err = db.Conn.Exec("DELETE FROM tag_post WHERE post_id=?", post.ID)
if err != nil {
logger.Warn("delete post tags error", "error", err)
}
for _, tag := range post.Tags {
_, err = db.Conn.Exec("INSERT INTO tag_post (tag_id,post_id) VALUES (?,?)", tag, post.ID)
if err != nil {
logger.Warn("add post tag error", "error", err)
}
}
return map[string]string{
"username": user.Username,
"id": post.ID,
}, nil
}
func DeletePost(id string) *e.Error {
_, err := db.Conn.Exec("DELETE FROM posts WHERE id=?", id)
if err != nil {
logger.Warn("delete post error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
// delete tags
_, err = db.Conn.Exec("DELETE FROM tag_post WHERE post_id=?", id)
if err != nil {
logger.Warn("delete post tags error", "error", err)
}
return nil
}
func GetPost(id string, slug string) (*models.Post, *e.Error) {
ar := &models.Post{}
var rawmd []byte
err := db.Conn.QueryRow("select id,slug,title,md,url,cover,brief,creator,likes,views,created,updated from posts where id=? or slug=?", id, slug).Scan(
&ar.ID, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.Likes, &ar.Views, &ar.Created, &ar.Updated,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, e.New(http.StatusNotFound, e.NotFound)
}
logger.Warn("get post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
md, _ := utils.Uncompress(rawmd)
ar.Md = string(md)
ar.Creator = &models.UserSimple{ID: ar.CreatorID}
err = ar.Creator.Query()
// get tags
t := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", ar.ID)
if err != nil && err != sql.ErrNoRows {
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
ar.RawTags = make([]*models.Tag, 0)
for rows.Next() {
var tag int64
err = rows.Scan(&tag)
t = append(t, tag)
rawTag, err := tags.GetTag(tag, "")
if err == nil {
ar.RawTags = append(ar.RawTags, rawTag)
}
}
ar.Tags = t
// add views count
_, err = db.Conn.Exec("UPDATE posts SET views=? WHERE id=?", ar.Views+1, ar.ID)
if err != nil {
logger.Warn("update post view count error", "error", err)
}
return ar, nil
}
func GetPostCreator(id string) (int64, *e.Error) {
var uid int64
err := db.Conn.QueryRow("SELECT creator FROM posts WHERE id=?", id).Scan(&uid)
if err != nil {
if err == sql.ErrNoRows {
return 0, e.New(http.StatusNotFound, e.NotFound)
}
logger.Warn("get post creator error", "error", err)
return 0, e.New(http.StatusInternalServerError, e.Internal)
}
return uid, nil
}
func HomePosts(filter string) (models.Posts, *e.Error) {
ars := make(models.Posts, 0)
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,creator,created,updated from posts")
if err != nil && err != sql.ErrNoRows {
logger.Warn("get user posts error", "error", err)
return ars, e.New(http.StatusInternalServerError, e.Internal)
}
for rows.Next() {
ar := &models.Post{}
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.CreatorID, &ar.Created, &ar.Updated)
if err != nil {
logger.Warn("scan post error", "error", err)
continue
}
creator := &models.UserSimple{ID: ar.CreatorID}
creator.Query()
ar.Creator = creator
ar.Comments = GetCommentCount(ar.ID)
ars = append(ars, ar)
}
sort.Sort(ars)
return ars, nil
}
func postExist(id string) bool {
var nid string
err := db.Conn.QueryRow("SELECT id from posts WHERE id=?", id).Scan(&nid)
if err != nil {
logger.Warn("query post error", "error", err)
return false
}
if nid != id {
return false
}
return true
}
//slug有三个规则
// 1. 长度不能超过127
// 2. 每次title更新都要重新生成slug
// 3. 单个用户下的slug不能重复如果已经存在需要加上-1这种字符
func setSlug(creator int64, post *models.Post) error {
slug := utils.Slugify(post.Title)
if len(slug) > 100 {
slug = slug[:100]
}
count := 0
err := db.Conn.QueryRow("SELECT count(*) FROM posts WHERE creator=? and title=?", creator, post.Title).Scan(&count)
if err != nil {
logger.Warn("count slug error", "error", err)
return err
}
if count == 0 {
post.Slug = slug
} else {
post.Slug = fmt.Sprintf("%s-%d", slug, count)
}
return nil
}