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.

261 lines
7.0 KiB

4 years ago
package story
4 years ago
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/session"
4 years ago
"github.com/imdotdev/im.dev/server/internal/tags"
4 years ago
"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)
4 years ago
rows, err := db.Conn.Query("select id,slug,title,url,cover,brief,likes,views,created,updated from posts where creator=?", uid)
4 years ago
if err != nil {
if err == sql.ErrNoRows {
return ars, e.New(http.StatusNotFound, e.NotFound)
}
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{}
4 years ago
err := rows.Scan(&ar.ID, &ar.Slug, &ar.Title, &ar.URL, &ar.Cover, &ar.Brief, &ar.Likes, &ar.Views, &ar.Created, &ar.Updated)
4 years ago
if err != nil {
logger.Warn("scan post error", "error", err)
continue
}
ar.Creator = creator
ars = append(ars, ar)
}
sort.Sort(ars)
return ars, nil
}
func SubmitPost(c *gin.Context) (map[string]string, *e.Error) {
user := session.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)
4 years ago
4 years ago
if post.ID == "" {
post.ID = utils.GenStoryID(models.StoryPost)
4 years ago
//create
4 years ago
_, 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)
4 years ago
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)
}
}
4 years ago
//update tags
// "tag_post": `CREATE TABLE IF NOT EXISTS tag_post (
// tag_id INTEGER,
// post_id INTEGER
// );
_, 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)
}
}
4 years ago
return map[string]string{
"username": user.Username,
4 years ago
"id": post.ID,
4 years ago
}, nil
}
4 years ago
func DeletePost(id string) *e.Error {
4 years ago
_, 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)
}
return nil
}
4 years ago
func GetPost(id string, slug string) (*models.Post, *e.Error) {
4 years ago
ar := &models.Post{}
var rawmd []byte
4 years ago
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,
4 years ago
)
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()
4 years ago
// get tags
4 years ago
t := make([]int64, 0)
rows, err := db.Conn.Query("SELECT tag_id FROM tag_post WHERE post_id=?", ar.ID)
4 years ago
if err != nil && err != sql.ErrNoRows {
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
4 years ago
ar.RawTags = make([]*models.Tag, 0)
4 years ago
for rows.Next() {
var tag int64
err = rows.Scan(&tag)
4 years ago
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)
4 years ago
}
4 years ago
return ar, nil
}
4 years ago
func GetPostCreator(id string) (int64, *e.Error) {
4 years ago
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
}
4 years ago
func postExist(id string) bool {
var nid string
4 years ago
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
}
4 years ago
if nid != id {
4 years ago
return false
}
return true
}
4 years ago
//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
}