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.

286 lines
8.0 KiB

4 years ago
package story
4 years ago
import (
"database/sql"
"fmt"
"net/http"
"strings"
"time"
"unicode/utf8"
"github.com/asaskevich/govalidator"
"github.com/gin-gonic/gin"
4 years ago
"github.com/imdotdev/im.dev/server/internal/interaction"
4 years ago
"github.com/imdotdev/im.dev/server/internal/org"
4 years ago
"github.com/imdotdev/im.dev/server/internal/tags"
4 years ago
"github.com/imdotdev/im.dev/server/internal/user"
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"
)
4 years ago
func SubmitStory(c *gin.Context) (map[string]string, *e.Error) {
4 years ago
user := user.CurrentUser(c)
4 years ago
4 years ago
post := &models.Story{}
4 years ago
err := c.Bind(&post)
if err != nil {
return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
}
4 years ago
if !models.ValidStoryIDType(post.Type) {
return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
}
4 years ago
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 {
4 years ago
// external post, need editor role
if !user.Role.IsEditor() {
4 years ago
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
} else {
4 years ago
// internal post, need creator role
if !user.Role.IsCreator() {
4 years ago
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
4 years ago
if post.Type == models.IDTypePost {
if len(post.Md) <= config.Data.Posts.BriefMaxLen {
post.Brief = post.Md
} else {
post.Brief = string([]rune(post.Md)[:config.Data.Posts.BriefMaxLen])
}
4 years ago
}
}
4 years ago
post.CreatorID = user.ID
4 years ago
// check user is in org exist
if post.OwnerID != "" {
4 years ago
if models.GetIDType(post.ID) == models.IDTypeSeries {
// 组织的series所有权和创作权都归组织所有
post.CreatorID = post.OwnerID
if !org.IsOrgAdmin(user.ID, post.OwnerID) {
return nil, e.New(http.StatusForbidden, e.NoAdminPermission)
}
} else {
if !org.UserInOrg(user.ID, post.OwnerID) {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
4 years ago
}
}
4 years ago
now := time.Now()
md := utils.Compress(post.Md)
setSlug(user.ID, post)
4 years ago
4 years ago
exist := models.IdExist(post.ID)
if !exist {
if post.ID == "" {
post.ID = utils.GenID(post.Type)
}
4 years ago
//create
4 years ago
_, err := db.Conn.Exec("INSERT INTO story (id,type,creator,owner,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)",
4 years ago
post.ID, post.Type, post.CreatorID, post.OwnerID, 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)
4 years ago
if creator != post.CreatorID {
4 years ago
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
4 years ago
_, err = db.Conn.Exec("UPDATE story SET owner=?, slug=?, title=?, md=?, url=?, cover=?, brief=?, updated=? WHERE id=?",
post.OwnerID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, now, post.ID)
4 years ago
if err != nil {
logger.Warn("upate post error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
}
4 years ago
//update tags
err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags)
4 years ago
if err != nil {
logger.Warn("upate tags error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
4 years ago
}
4 years ago
return map[string]string{
"username": user.Username,
4 years ago
"id": post.ID,
4 years ago
}, nil
}
4 years ago
func SubmitPostDraft(c *gin.Context) (map[string]string, *e.Error) {
user := user.CurrentUser(c)
post := &models.Story{}
err := c.Bind(&post)
if err != nil {
return nil, e.New(http.StatusBadRequest, e.ParamInvalid)
}
if !user.Role.IsCreator() {
return nil, e.New(http.StatusForbidden, e.NoEditorPermission)
}
md := utils.Compress(post.Md)
now := time.Now()
if len(post.Md) <= config.Data.Posts.BriefMaxLen {
post.Brief = post.Md
} else {
post.Brief = string([]rune(post.Md)[:config.Data.Posts.BriefMaxLen])
}
if post.ID == "" {
post.ID = utils.GenID(models.IDTypePost)
//create
_, err := db.Conn.Exec("INSERT INTO story (id,type,creator,slug, title, md, url, cover, brief,status, created, updated) VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
post.ID, models.IDTypePost, user.ID, post.Slug, post.Title, md, post.URL, post.Cover, post.Brief, models.StatusDraft, now, now)
fmt.Println(post.Brief)
if err != nil {
logger.Warn("submit post draft 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 story 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 draft error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
}
//update tags
err = tags.UpdateTargetTags(user.ID, post.ID, post.Tags)
if err != nil {
logger.Warn("upate tags error", "error", err)
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
return map[string]string{
"id": post.ID,
}, nil
}
4 years ago
func DeletePost(id string) *e.Error {
4 years ago
tx, err := db.Conn.Begin()
if err != nil {
logger.Warn("start sql transaction error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
4 years ago
_, err = tx.Exec("DELETE FROM story WHERE id=?", id)
4 years ago
if err != nil {
logger.Warn("delete post error", "error", err)
return e.New(http.StatusInternalServerError, e.Internal)
}
4 years ago
// delete tags
4 years ago
err = tags.DeleteTargetTags(tx, id)
4 years ago
if err != nil {
logger.Warn("delete post tags error", "error", err)
4 years ago
tx.Rollback()
4 years ago
}
4 years ago
tx.Commit()
4 years ago
return nil
}
4 years ago
func GetStory(id string, slug string) (*models.Story, *e.Error) {
ar := &models.Story{}
4 years ago
var rawmd []byte
4 years ago
err := db.Conn.QueryRow("select id,type,slug,title,md,url,cover,brief,creator,owner,status,created,updated from story where id=?", id).Scan(
&ar.ID, &ar.Type, &ar.Slug, &ar.Title, &rawmd, &ar.URL, &ar.Cover, &ar.Brief, &ar.CreatorID, &ar.OwnerID, &ar.Status, &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)
4 years ago
4 years ago
ar.Creator = &models.UserSimple{ID: ar.CreatorID}
err = ar.Creator.Query()
4 years ago
if ar.OwnerID != "" {
ar.Owner = &models.UserSimple{ID: ar.OwnerID}
err = ar.Owner.Query()
}
4 years ago
4 years ago
// get tags
t, rawTags, err := tags.GetTargetTags(ar.ID)
if err != nil {
4 years ago
return nil, e.New(http.StatusInternalServerError, e.Internal)
}
4 years ago
ar.Tags = t
ar.RawTags = rawTags
4 years ago
4 years ago
ar.Likes = interaction.GetLikes(ar.ID)
4 years ago
return ar, nil
}
4 years ago
func GetPostCreator(id string) (string, *e.Error) {
var uid string
4 years ago
err := db.Conn.QueryRow("SELECT creator FROM story WHERE id=?", id).Scan(&uid)
4 years ago
if err != nil {
if err == sql.ErrNoRows {
4 years ago
return "", e.New(http.StatusNotFound, e.NotFound)
4 years ago
}
logger.Warn("get post creator error", "error", err)
4 years ago
return "", e.New(http.StatusInternalServerError, e.Internal)
4 years ago
}
return uid, nil
}
//slug有三个规则
// 1. 长度不能超过127
// 2. 每次title更新都要重新生成slug
// 3. 单个用户下的slug不能重复如果已经存在需要加上-1这种字符
4 years ago
func setSlug(creator string, post *models.Story) error {
4 years ago
slug := utils.Slugify(post.Title)
if len(slug) > 100 {
slug = slug[:100]
}
4 years ago
post.Slug = slug
4 years ago
return nil
}