Skip to content
Permalink
Browse files

add article write/edit/view

  • Loading branch information...
sunface committed Sep 6, 2019
1 parent 123f177 commit ce097358cacb816c2e3ea3d3d1593b774d157d39
Showing with 2,180 additions and 128 deletions.
  1. +0 −1 conf.yaml
  2. +13 −0 internal/api_handler.go
  3. +19 −1 internal/ecode/ecode.go
  4. +3 −0 internal/internal.go
  5. +11 −0 internal/misc/cql.go
  6. +21 −0 internal/misc/id.go
  7. +0 −4 internal/misc/var.go
  8. +241 −0 internal/post/article.go
  9. +168 −0 internal/post/comment.go
  10. +15 −0 internal/post/const.go
  11. +67 −0 internal/post/post.go
  12. +18 −7 internal/session/session.go
  13. +66 −0 internal/session/user.go
  14. +35 −0 internal/utils/string.go
  15. +44 −0 internal/utils/time.go
  16. +0 −15 quick-start/cql/init.cql
  17. +76 −0 quick-start/cql/start.cql
  18. +1 −1 ui/config/index.js
  19. +1 −1 ui/index.html
  20. +19 −0 ui/package-lock.json
  21. +1 −0 ui/package.json
  22. +2 −1 ui/src/lang/en.js
  23. +3 −2 ui/src/lang/zh.js
  24. +7 −0 ui/src/main.js
  25. +30 −5 ui/src/router/index.js
  26. +3 −1 ui/src/store/index.js
  27. +35 −0 ui/src/store/modules/misc.js
  28. +19 −0 ui/src/store/modules/post.js
  29. +19 −1 ui/src/store/modules/user.js
  30. +6 −0 ui/src/theme/common_layout.less
  31. +30 −0 ui/src/theme/editor.css
  32. +6 −1 ui/src/theme/light/style.less
  33. +420 −0 ui/src/theme/md_render.css
  34. +57 −0 ui/src/theme/style.less
  35. +6 −0 ui/src/utils/data.js
  36. +3 −8 ui/src/utils/request.js
  37. +221 −0 ui/src/views/article/detail.vue
  38. +230 −0 ui/src/views/article/edit.vue
  39. +102 −0 ui/src/views/components/discuss.vue
  40. +62 −0 ui/src/views/components/editor.vue
  41. +43 −0 ui/src/views/components/render.vue
  42. +0 −68 ui/src/views/errorPage/page404.vue
  43. +57 −11 ui/src/views/nav.vue
@@ -2,7 +2,6 @@ version: 0.1.0
log_level: debug

listen_addr: ":10086"

cql:
cluster:
- "10.77.64.46:9042"
@@ -2,11 +2,24 @@ package internal

import (
"github.com/labstack/echo"
"github.com/thinkindev/im.dev/internal/post"
"github.com/thinkindev/im.dev/internal/session"
)

func apiHandler(e *echo.Echo) {
// sign-in apis
e.POST("/web/signIn", session.SignIn)
e.POST("/web/signOut", session.SignOut)
e.GET("/web/user/get", session.GetUser)

// article apis
e.POST("/web/article/saveNew", post.NewArticle, session.CheckSignIn)
e.POST("/web/post/preview", post.Preview, session.CheckSignIn)
e.GET("/web/article/detail", post.GetArticleDetail)
e.GET("/web/article/beforeEdit", post.BeforeEditAr, session.CheckSignIn)
e.POST("/web/article/saveChanges", post.SaveArticleChanges, session.CheckSignIn)

// comment apis
e.POST("/web/post/comment", post.Comment, session.CheckSignIn)
e.GET("/web/post/queryComments", post.QueryComments)
}
@@ -1,7 +1,25 @@
package ecode

// account
// common
const (
NotSignIn = 1001
NotSignInMsg = "user need sign in"

ParamInvalid = 1002
ParamInvalidMsg = "param invalid"

CommonErrorMsg = "Oh no, an unexpected error happend"
DatabaseError = 1003

NoPermission = 1004
NoPermissionMsg = "You don't have permission"
)

// article
const (
ArticleNotFound = 1100
ArticleNotFoundMsg = "Target article not found"

PostNotFound = 1101
PostNotFoundMsg = "Target post not found"
)
@@ -9,6 +9,7 @@ import (
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"github.com/thinkindev/im.dev/internal/misc"
"github.com/thinkindev/im.dev/internal/session"
)

// Start web server for im.dev ui
@@ -28,6 +29,8 @@ func Start(confPath string) {
}
}

session.InitUser()

e := echo.New()
e.Pre(middleware.RemoveTrailingSlash())
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{Level: 5}))
@@ -0,0 +1,11 @@
package misc

import "github.com/gocql/gocql"

// CQL is the cql session for access cassandra
var CQL *gocql.Session

const (
//CQLNotFound when acess cql, get the not found result
CQLNotFound = "not found"
)
@@ -0,0 +1,21 @@
package misc

import (
"fmt"

"github.com/sony/sonyflake"
)

// IDGen generate ditrubuted unique id
var IDGen *sonyflake.Sonyflake

func init() {
var st sonyflake.Settings
IDGen = sonyflake.NewSonyflake(st)
}

// GenID return a hex number string
func GenID() string {
id, _ := IDGen.NextID()
return fmt.Sprintf("%x", id)
}
@@ -3,7 +3,6 @@ package misc
import (
"encoding/base64"

"github.com/gocql/gocql"
"go.uber.org/zap"
)

@@ -15,6 +14,3 @@ var Log *zap.Logger

// Base64 is the base64 handler
var Base64 = base64.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")

// CQL is the cql session for access cassandra
var CQL *gocql.Session
@@ -0,0 +1,241 @@
package post

import (
"encoding/json"
"net/http"
"time"

"github.com/labstack/echo"
"github.com/thinkindev/im.dev/internal/ecode"
"github.com/thinkindev/im.dev/internal/misc"
"github.com/thinkindev/im.dev/internal/session"
"github.com/thinkindev/im.dev/internal/utils"
"go.uber.org/zap"
)

// ArContent represent article content
type ArContent struct {
ID string `json:"id"`
Title string `json:"title"`
Tags []string `json:"tags"`
MD string `json:"md"`
Render string `json:"render"`
Lang string `json:"lang"`
Status int `json:"status"`
}

// NewArticle create a new article
func NewArticle(c echo.Context) error {
opType := c.FormValue("type")
if opType != "1" && opType != "2" {
return c.JSON(http.StatusBadRequest, misc.HTTPResp{
ErrCode: ecode.ParamInvalid,
Message: ecode.ParamInvalidMsg,
})
}

content := c.FormValue("content")
ar := &ArContent{}
err := json.Unmarshal([]byte(content), &ar)
if err != nil {
return c.JSON(http.StatusBadRequest, misc.HTTPResp{
ErrCode: ecode.ParamInvalid,
Message: ecode.ParamInvalidMsg,
})
}

sess := session.Get(c)

// generate id for article
ar.ID = misc.GenID()

// modify render
ar.Render = modify(ar.Render)

err = misc.CQL.Query(`INSERT INTO article (id,uid,title,tags,md,render,status,edit_date,lang)
VALUES (?,?,?,?,?,?,?,?,?)`, ar.ID, sess.ID, ar.Title, ar.Tags, ar.MD, ar.Render, opType, time.Now().Unix(), ar.Lang).Exec()
if err != nil {
misc.Log.Warn("access database error", zap.Error(err))
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.DatabaseError,
Message: ecode.CommonErrorMsg,
})
}

saveTags(ar.ID, ar.Tags)
return c.JSON(http.StatusOK, misc.HTTPResp{
Data: sess.Name + "/" + ar.ID,
})
}

// ArticleDetail contains detail data of article
type ArticleDetail struct {
ID string `json:"id"`
UID string `json:"uid"`
Title string `json:"title"`
Tags []string `json:"tags"`
Render string `json:"render"`
Status int `json:"status"`
PublishDate string `json:"publish_date"`
EditDate string `json:"edit_date"`
Lang string `json:"lang"`
pubDate int64
editDate int64
}

// GetArticleDetail return detail data of the article
func GetArticleDetail(c echo.Context) error {
arID := c.FormValue("article_id")
if arID == "" {
return c.JSON(http.StatusBadRequest, misc.HTTPResp{
ErrCode: ecode.ParamInvalid,
Message: ecode.ParamInvalidMsg,
})
}

detail := &ArticleDetail{ID: arID}
err := misc.CQL.Query(`SELECT uid,title,tags,render,status,publish_date,edit_date,lang FROM article WHERE id=?`, arID).Scan(
&detail.UID, &detail.Title, &detail.Tags, &detail.Render, &detail.Status, &detail.pubDate, &detail.editDate, &detail.Lang,
)
if err != nil {
if err.Error() == misc.CQLNotFound {
return c.JSON(http.StatusNotFound, misc.HTTPResp{
ErrCode: ecode.ArticleNotFound,
Message: ecode.ArticleNotFoundMsg,
})
}
misc.Log.Warn("access database error", zap.Error(err))
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.DatabaseError,
Message: ecode.CommonErrorMsg,
})
}

if detail.pubDate != 0 {
detail.PublishDate = utils.Time2ReadableString(time.Unix(detail.pubDate, 0))
}
if detail.editDate != 0 {
detail.EditDate = utils.Time2ReadableString(time.Unix(detail.editDate, 0))
}
return c.JSON(http.StatusOK, misc.HTTPResp{
Data: detail,
})
}

// BeforeEditAr deal some pre-things before editing article
func BeforeEditAr(c echo.Context) error {
arID := c.FormValue("article_id")
if arID == "" {
return c.JSON(http.StatusBadRequest, misc.HTTPResp{
ErrCode: ecode.ParamInvalid,
Message: ecode.ParamInvalidMsg,
})
}

var uid, title, md, lang string
var tags []string
var status int
err := misc.CQL.Query(`SELECT uid,title,tags,md,lang,status FROM article WHERE id=?`, arID).Scan(
&uid, &title, &tags, &md, &lang, &status,
)
if err != nil {
if err.Error() == misc.CQLNotFound {
return c.JSON(http.StatusNotFound, misc.HTTPResp{
ErrCode: ecode.ArticleNotFound,
Message: ecode.ArticleNotFoundMsg,
})
}
misc.Log.Warn("access database error", zap.Error(err))
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.DatabaseError,
Message: ecode.CommonErrorMsg,
})
}

sess := session.Get(c)

// check whether user has permission to do so
if uid != sess.ID {
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.NoPermission,
Message: ecode.NoPermissionMsg,
})
}

ar := &ArContent{
ID: arID,
Title: title,
MD: md,
Tags: tags,
Lang: lang,
Status: status,
}

return c.JSON(http.StatusOK, misc.HTTPResp{
Data: ar,
})
}

// SaveArticleChanges save changes when edit article
func SaveArticleChanges(c echo.Context) error {
content := c.FormValue("content")
ar := &ArContent{}
err := json.Unmarshal([]byte(content), &ar)
if err != nil {
return c.JSON(http.StatusBadRequest, misc.HTTPResp{
ErrCode: ecode.ParamInvalid,
Message: ecode.ParamInvalidMsg,
})
}

// check the article is exist and user has permission
var uid string
err = misc.CQL.Query(`SELECT uid FROM article WHERE id=?`, ar.ID).Scan(&uid)
if err != nil {
if err.Error() == misc.CQLNotFound {
return c.JSON(http.StatusNotFound, misc.HTTPResp{
ErrCode: ecode.ArticleNotFound,
Message: ecode.ArticleNotFoundMsg,
})
}
misc.Log.Warn("access database error", zap.Error(err))
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.DatabaseError,
Message: ecode.CommonErrorMsg,
})
}
sess := session.Get(c)
if sess.ID != uid {
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.NoPermission,
Message: ecode.NoPermissionMsg,
})
}

// modify render
ar.Render = modify(ar.Render)

err = misc.CQL.Query(`UPDATE article SET title=?,tags=?,md=?,render=?,edit_date=?,lang=? WHERE id=?`,
ar.Title, ar.Tags, ar.MD, ar.Render, time.Now().Unix(), ar.Lang, ar.ID).Exec()
if err != nil {
misc.Log.Warn("access database error", zap.Error(err))
return c.JSON(http.StatusInternalServerError, misc.HTTPResp{
ErrCode: ecode.DatabaseError,
Message: ecode.CommonErrorMsg,
})
}

saveTags(ar.ID, ar.Tags)
return c.JSON(http.StatusOK, misc.HTTPResp{
Data: sess.Name + "/" + ar.ID,
})
}

func saveTags(arID string, tags []string) {
for _, tag := range tags {
err := misc.CQL.Query(`INSERT INTO tags (name,article_id) VALUES (?,?)`, tag, arID).Exec()
if err != nil {
misc.Log.Warn("access database error", zap.Error(err))
}
}
}

0 comments on commit ce09735

Please sign in to comment.
You can’t perform that action at this time.