diff --git a/docs/sql/update.20200418.sql b/docs/sql/update.20200418.sql
new file mode 100644
index 0000000000000000000000000000000000000000..6ae93ebfb17bdec550cd14aa837ace7b502ab588
--- /dev/null
+++ b/docs/sql/update.20200418.sql
@@ -0,0 +1,13 @@
+rename table t_topic_like to t_user_like;
+
+alter table t_user_like
+ add column entity_type varchar(32) not null after user_id,
+ change column topic_id entity_id bigint not null after entity_type,
+ drop index idx_topic_like_user_id,
+ drop index idx_topic_like_topic_id;
+
+update t_user_like set entity_type = 'topic';
+
+alter table t_topic
+ drop column type,
+ drop column image_list;
diff --git a/server/app/iris.go b/server/app/iris.go
index ed677846d910164de3a77884a003e131d5af2e73..7ee5f5214d1ca098a9141f6b13e06eed12d525b3 100644
--- a/server/app/iris.go
+++ b/server/app/iris.go
@@ -1,139 +1,140 @@
-package app
-
-import (
- "net/http"
- "os"
- "os/signal"
- "strings"
- "syscall"
-
- "github.com/go-resty/resty/v2"
-
- "github.com/iris-contrib/middleware/cors"
- "github.com/kataras/iris/v12"
- "github.com/kataras/iris/v12/middleware/logger"
- "github.com/kataras/iris/v12/middleware/recover"
- "github.com/kataras/iris/v12/mvc"
- "github.com/mlogclub/simple"
- "github.com/sirupsen/logrus"
-
- "bbs-go/common/config"
- "bbs-go/controllers/api"
-
- "bbs-go/controllers/admin"
- "bbs-go/middleware"
-)
-
-func InitIris() {
- app := iris.New()
- app.Logger().SetLevel("warn")
- app.Use(recover.New())
- app.Use(logger.New())
- app.Use(cors.New(cors.Options{
- AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
- AllowCredentials: true,
- MaxAge: 600,
- AllowedMethods: []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut},
- AllowedHeaders: []string{"*"},
- }))
- app.AllowMethods(iris.MethodOptions)
-
- app.OnAnyErrorCode(func(ctx iris.Context) {
- path := ctx.Path()
- var err error
- if strings.Contains(path, "/api/admin/") {
- _, err = ctx.JSON(simple.JsonErrorCode(ctx.GetStatusCode(), "Http error"))
- }
- if err != nil {
- logrus.Error(err)
- }
- })
-
- app.Any("/", func(i iris.Context) {
- _, _ = i.HTML("
Powered by bbs-go
")
- })
-
- // api
- mvc.Configure(app.Party("/api"), func(m *mvc.Application) {
- m.Party("/topic").Handle(new(api.TopicController))
- m.Party("/article").Handle(new(api.ArticleController))
- m.Party("/project").Handle(new(api.ProjectController))
- m.Party("/login").Handle(new(api.LoginController))
- m.Party("/user").Handle(new(api.UserController))
- m.Party("/tag").Handle(new(api.TagController))
- m.Party("/comment").Handle(new(api.CommentController))
- m.Party("/favorite").Handle(new(api.FavoriteController))
- m.Party("/config").Handle(new(api.ConfigController))
- m.Party("/upload").Handle(new(api.UploadController))
- m.Party("/link").Handle(new(api.LinkController))
- m.Party("/captcha").Handle(new(api.CaptchaController))
- m.Party("/spider").Handle(new(api.SpiderController))
- })
-
- // admin
- mvc.Configure(app.Party("/api/admin"), func(m *mvc.Application) {
- m.Router.Use(middleware.AdminAuth)
- m.Party("/common").Handle(new(admin.CommonController))
- m.Party("/user").Handle(new(admin.UserController))
- m.Party("/third-account").Handle(new(admin.ThirdAccountController))
- m.Party("/tag").Handle(new(admin.TagController))
- m.Party("/article").Handle(new(admin.ArticleController))
- m.Party("/comment").Handle(new(admin.CommentController))
- m.Party("/favorite").Handle(new(admin.FavoriteController))
- m.Party("/article-tag").Handle(new(admin.ArticleTagController))
- m.Party("/topic").Handle(new(admin.TopicController))
- m.Party("/topic-node").Handle(new(admin.TopicNodeController))
- m.Party("/sys-config").Handle(new(admin.SysConfigController))
- m.Party("/link").Handle(new(admin.LinkController))
- m.Party("/user-score").Handle(new(admin.UserScoreController))
- m.Party("/user-score-log").Handle(new(admin.UserScoreLogController))
- })
-
- app.Get("/api/img/proxy", func(i iris.Context) {
- url := i.FormValue("url")
- resp, err := resty.New().R().Get(url)
- i.Header("Content-Type", "image/jpg")
- if err == nil {
- _, _ = i.Write(resp.Body())
- } else {
- logrus.Error(err)
- }
- })
-
- server := &http.Server{Addr: ":" + config.Conf.Port}
- handleSignal(server)
- err := app.Run(iris.Server(server), iris.WithConfiguration(iris.Configuration{
- DisableStartupLog: false,
- DisableInterruptHandler: false,
- DisablePathCorrection: false,
- EnablePathEscape: false,
- FireMethodNotAllowed: false,
- DisableBodyConsumptionOnUnmarshal: false,
- DisableAutoFireStatusCode: false,
- EnableOptimizations: true,
- TimeFormat: "2006-01-02 15:04:05",
- Charset: "UTF-8",
- }))
- if err != nil {
- logrus.Error(err)
- os.Exit(-1)
- }
-}
-
-func handleSignal(server *http.Server) {
- c := make(chan os.Signal)
- signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
-
- go func() {
- s := <-c
- logrus.Infof("got signal [%s], exiting now", s)
- if err := server.Close(); nil != err {
- logrus.Errorf("server close failed: " + err.Error())
- }
-
- simple.CloseDB()
-
- logrus.Infof("Exited")
- os.Exit(0)
- }()
-}
+package app
+
+import (
+ "net/http"
+ "os"
+ "os/signal"
+ "strings"
+ "syscall"
+
+ "github.com/go-resty/resty/v2"
+
+ "github.com/iris-contrib/middleware/cors"
+ "github.com/kataras/iris/v12"
+ "github.com/kataras/iris/v12/middleware/logger"
+ "github.com/kataras/iris/v12/middleware/recover"
+ "github.com/kataras/iris/v12/mvc"
+ "github.com/mlogclub/simple"
+ "github.com/sirupsen/logrus"
+
+ "bbs-go/common/config"
+ "bbs-go/controllers/api"
+
+ "bbs-go/controllers/admin"
+ "bbs-go/middleware"
+)
+
+func InitIris() {
+ app := iris.New()
+ app.Logger().SetLevel("warn")
+ app.Use(recover.New())
+ app.Use(logger.New())
+ app.Use(cors.New(cors.Options{
+ AllowedOrigins: []string{"*"}, // allows everything, use that to change the hosts.
+ AllowCredentials: true,
+ MaxAge: 600,
+ AllowedMethods: []string{iris.MethodGet, iris.MethodPost, iris.MethodOptions, iris.MethodHead, iris.MethodDelete, iris.MethodPut},
+ AllowedHeaders: []string{"*"},
+ }))
+ app.AllowMethods(iris.MethodOptions)
+
+ app.OnAnyErrorCode(func(ctx iris.Context) {
+ path := ctx.Path()
+ var err error
+ if strings.Contains(path, "/api/admin/") {
+ _, err = ctx.JSON(simple.JsonErrorCode(ctx.GetStatusCode(), "Http error"))
+ }
+ if err != nil {
+ logrus.Error(err)
+ }
+ })
+
+ app.Any("/", func(i iris.Context) {
+ _, _ = i.HTML("Powered by bbs-go
")
+ })
+
+ // api
+ mvc.Configure(app.Party("/api"), func(m *mvc.Application) {
+ m.Party("/topic").Handle(new(api.TopicController))
+ m.Party("/tweet").Handle(new(api.TweetController))
+ m.Party("/article").Handle(new(api.ArticleController))
+ m.Party("/project").Handle(new(api.ProjectController))
+ m.Party("/login").Handle(new(api.LoginController))
+ m.Party("/user").Handle(new(api.UserController))
+ m.Party("/tag").Handle(new(api.TagController))
+ m.Party("/comment").Handle(new(api.CommentController))
+ m.Party("/favorite").Handle(new(api.FavoriteController))
+ m.Party("/config").Handle(new(api.ConfigController))
+ m.Party("/upload").Handle(new(api.UploadController))
+ m.Party("/link").Handle(new(api.LinkController))
+ m.Party("/captcha").Handle(new(api.CaptchaController))
+ m.Party("/spider").Handle(new(api.SpiderController))
+ })
+
+ // admin
+ mvc.Configure(app.Party("/api/admin"), func(m *mvc.Application) {
+ m.Router.Use(middleware.AdminAuth)
+ m.Party("/common").Handle(new(admin.CommonController))
+ m.Party("/user").Handle(new(admin.UserController))
+ m.Party("/third-account").Handle(new(admin.ThirdAccountController))
+ m.Party("/tag").Handle(new(admin.TagController))
+ m.Party("/article").Handle(new(admin.ArticleController))
+ m.Party("/comment").Handle(new(admin.CommentController))
+ m.Party("/favorite").Handle(new(admin.FavoriteController))
+ m.Party("/article-tag").Handle(new(admin.ArticleTagController))
+ m.Party("/topic").Handle(new(admin.TopicController))
+ m.Party("/topic-node").Handle(new(admin.TopicNodeController))
+ m.Party("/sys-config").Handle(new(admin.SysConfigController))
+ m.Party("/link").Handle(new(admin.LinkController))
+ m.Party("/user-score").Handle(new(admin.UserScoreController))
+ m.Party("/user-score-log").Handle(new(admin.UserScoreLogController))
+ })
+
+ app.Get("/api/img/proxy", func(i iris.Context) {
+ url := i.FormValue("url")
+ resp, err := resty.New().R().Get(url)
+ i.Header("Content-Type", "image/jpg")
+ if err == nil {
+ _, _ = i.Write(resp.Body())
+ } else {
+ logrus.Error(err)
+ }
+ })
+
+ server := &http.Server{Addr: ":" + config.Conf.Port}
+ handleSignal(server)
+ err := app.Run(iris.Server(server), iris.WithConfiguration(iris.Configuration{
+ DisableStartupLog: false,
+ DisableInterruptHandler: false,
+ DisablePathCorrection: false,
+ EnablePathEscape: false,
+ FireMethodNotAllowed: false,
+ DisableBodyConsumptionOnUnmarshal: false,
+ DisableAutoFireStatusCode: false,
+ EnableOptimizations: true,
+ TimeFormat: "2006-01-02 15:04:05",
+ Charset: "UTF-8",
+ }))
+ if err != nil {
+ logrus.Error(err)
+ os.Exit(-1)
+ }
+}
+
+func handleSignal(server *http.Server) {
+ c := make(chan os.Signal)
+ signal.Notify(c, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
+
+ go func() {
+ s := <-c
+ logrus.Infof("got signal [%s], exiting now", s)
+ if err := server.Close(); nil != err {
+ logrus.Errorf("server close failed: " + err.Error())
+ }
+
+ simple.CloseDB()
+
+ logrus.Infof("Exited")
+ os.Exit(0)
+ }()
+}
diff --git a/server/codegen/codegen.go b/server/codegen/codegen.go
index 3b37b6c66d22c8181669edd86b644346bc311022..ac02f1ca9fba74616147d00fbfb4c9d8546e60dd 100644
--- a/server/codegen/codegen.go
+++ b/server/codegen/codegen.go
@@ -1,12 +1,11 @@
-package main
-
-import (
- "github.com/mlogclub/simple"
-
- "bbs-go/model"
-)
-
-func main() {
- simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.UserScore{}))
- simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.UserScoreLog{}))
-}
+package main
+
+import (
+ "github.com/mlogclub/simple"
+
+ "bbs-go/model"
+)
+
+func main() {
+ simple.Generate("./", "bbs-go", simple.GetGenerateStruct(&model.Tweet{}))
+}
diff --git a/server/controllers/admin/topic_like_controller.go b/server/controllers/admin/tweet_controller.go
similarity index 58%
rename from server/controllers/admin/topic_like_controller.go
rename to server/controllers/admin/tweet_controller.go
index edafba97e3937e2d5e90a65f4409328c3305af8a..39d97928fdfde8316ecc234df5a168b05e09466b 100644
--- a/server/controllers/admin/topic_like_controller.go
+++ b/server/controllers/admin/tweet_controller.go
@@ -1,64 +1,64 @@
-package admin
-
-import (
- "strconv"
-
- "github.com/kataras/iris/v12"
- "github.com/mlogclub/simple"
-
- "bbs-go/model"
- "bbs-go/services"
-)
-
-type TopicLikeController struct {
- Ctx iris.Context
-}
-
-func (c *TopicLikeController) GetBy(id int64) *simple.JsonResult {
- t := services.TopicLikeService.Get(id)
- if t == nil {
- return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10))
- }
- return simple.JsonData(t)
-}
-
-func (c *TopicLikeController) AnyList() *simple.JsonResult {
- list, paging := services.TopicLikeService.FindPageByParams(simple.NewQueryParams(c.Ctx).PageByReq().Desc("id"))
- return simple.JsonData(&simple.PageResult{Results: list, Page: paging})
-}
-
-func (c *TopicLikeController) PostCreate() *simple.JsonResult {
- t := &model.TopicLike{}
- err := simple.ReadForm(c.Ctx, t)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
-
- err = services.TopicLikeService.Create(t)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- return simple.JsonData(t)
-}
-
-func (c *TopicLikeController) PostUpdate() *simple.JsonResult {
- id, err := simple.FormValueInt64(c.Ctx, "id")
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- t := services.TopicLikeService.Get(id)
- if t == nil {
- return simple.JsonErrorMsg("entity not found")
- }
-
- err = simple.ReadForm(c.Ctx, t)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
-
- err = services.TopicLikeService.Update(t)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- return simple.JsonData(t)
-}
+package admin
+
+import (
+ "strconv"
+
+ "github.com/kataras/iris/v12"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/model"
+ "bbs-go/services"
+)
+
+type TweetController struct {
+ Ctx iris.Context
+}
+
+func (c *TweetController) GetBy(id int64) *simple.JsonResult {
+ t := services.TweetService.Get(id)
+ if t == nil {
+ return simple.JsonErrorMsg("Not found, id=" + strconv.FormatInt(id, 10))
+ }
+ return simple.JsonData(t)
+}
+
+func (c *TweetController) AnyList() *simple.JsonResult {
+ list, paging := services.TweetService.FindPageByParams(simple.NewQueryParams(c.Ctx).PageByReq().Desc("id"))
+ return simple.JsonData(&simple.PageResult{Results: list, Page: paging})
+}
+
+func (c *TweetController) PostCreate() *simple.JsonResult {
+ t := &model.Tweet{}
+ err := simple.ReadForm(c.Ctx, t)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+
+ err = services.TweetService.Create(t)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonData(t)
+}
+
+func (c *TweetController) PostUpdate() *simple.JsonResult {
+ id, err := simple.FormValueInt64(c.Ctx, "id")
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ t := services.TweetService.Get(id)
+ if t == nil {
+ return simple.JsonErrorMsg("entity not found")
+ }
+
+ err = simple.ReadForm(c.Ctx, t)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+
+ err = services.TweetService.Update(t)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonData(t)
+}
diff --git a/server/controllers/api/topic_controller.go b/server/controllers/api/topic_controller.go
index cb4112334756355f54fff9c663b28eb0432fa343..f553ad3e883df13b65ebbbeeaf24ee1815065e80 100644
--- a/server/controllers/api/topic_controller.go
+++ b/server/controllers/api/topic_controller.go
@@ -1,285 +1,280 @@
-package api
-
-import (
- "math/rand"
- "strings"
-
- "github.com/kataras/iris/v12"
- "github.com/mlogclub/simple"
-
- "bbs-go/controllers/render"
- "bbs-go/model"
- "bbs-go/services"
- "bbs-go/services/cache"
-)
-
-type TopicController struct {
- Ctx iris.Context
-}
-
-// 节点
-func (c *TopicController) GetNodes() *simple.JsonResult {
- nodes := services.TopicNodeService.GetNodes()
- return simple.JsonData(render.BuildNodes(nodes))
-}
-
-// 节点信息
-func (c *TopicController) GetNode() *simple.JsonResult {
- nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
- node := services.TopicNodeService.Get(nodeId)
- return simple.JsonData(render.BuildNode(node))
-}
-
-// 发表帖子
-func (c *TopicController) PostCreate() *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
- nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
- topicType := simple.FormValueIntDefault(c.Ctx, "type", model.TopicTypeNormal)
- title := strings.TrimSpace(simple.FormValue(c.Ctx, "title"))
- content := strings.TrimSpace(simple.FormValue(c.Ctx, "content"))
- imageList := simple.FormValue(c.Ctx, "imageList")
- tags := simple.FormValueStringArray(c.Ctx, "tags")
- topic, err := services.TopicService.Publish(topicType, user.Id, nodeId, tags, title, content, imageList)
- if err != nil {
- return simple.JsonError(err)
- }
- return simple.JsonData(render.BuildSimpleTopic(topic))
-}
-
-// 编辑时获取详情
-func (c *TopicController) GetEditBy(topicId int64) *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
-
- topic := services.TopicService.Get(topicId)
- if topic == nil || topic.Status != model.StatusOk {
- return simple.JsonErrorMsg("话题不存在或已被删除")
- }
- if topic.UserId != user.Id {
- return simple.JsonErrorMsg("无权限")
- }
-
- tags := services.TopicService.GetTopicTags(topicId)
- var tagNames []string
- if len(tags) > 0 {
- for _, tag := range tags {
- tagNames = append(tagNames, tag.Name)
- }
- }
-
- return simple.NewEmptyRspBuilder().
- Put("topicId", topic.Id).
- Put("nodeId", topic.NodeId).
- Put("title", topic.Title).
- Put("content", topic.Content).
- Put("tags", tagNames).
- JsonResult()
-}
-
-// 编辑帖子
-func (c *TopicController) PostEditBy(topicId int64) *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
-
- topic := services.TopicService.Get(topicId)
- if topic == nil || topic.Status != model.StatusOk {
- return simple.JsonErrorMsg("话题不存在或已被删除")
- }
- if topic.UserId != user.Id {
- return simple.JsonErrorMsg("无权限")
- }
- if topic.Type == model.TopicTypeTwitter {
- return simple.JsonErrorMsg("推文类型话题不允许修改")
- }
-
- nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
- title := strings.TrimSpace(simple.FormValue(c.Ctx, "title"))
- content := strings.TrimSpace(simple.FormValue(c.Ctx, "content"))
- tags := simple.FormValueStringArray(c.Ctx, "tags")
-
- err := services.TopicService.Edit(topicId, nodeId, tags, title, content)
- if err != nil {
- return simple.JsonError(err)
- }
- return simple.JsonData(render.BuildSimpleTopic(topic))
-}
-
-// 删除帖子
-func (c *TopicController) PostDeleteBy(topicId int64) *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
- topic := services.TopicService.Get(topicId)
- if topic == nil || topic.Status != model.StatusOk {
- return simple.JsonSuccess()
- }
- if topic.UserId != user.Id {
- return simple.JsonErrorMsg("无权限")
- }
- err := services.TopicService.Delete(topicId)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- return simple.JsonSuccess()
-}
-
-// 帖子详情
-func (c *TopicController) GetBy(topicId int64) *simple.JsonResult {
- topic := services.TopicService.Get(topicId)
- if topic == nil || topic.Status != model.StatusOk {
- return simple.JsonErrorMsg("主题不存在")
- }
- services.TopicService.IncrViewCount(topicId) // 增加浏览量
- return simple.JsonData(render.BuildTopic(topic))
-}
-
-// 点赞
-func (c *TopicController) PostLikeBy(topicId int64) *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
- err := services.TopicLikeService.Like(user.Id, topicId)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- return simple.JsonSuccess()
-}
-
-// 点赞用户
-func (c *TopicController) GetRecentlikesBy(topicId int64) *simple.JsonResult {
- topicLikes := services.TopicLikeService.Recent(topicId, 10)
- var users []model.UserInfo
- for _, topicLike := range topicLikes {
- userInfo := render.BuildUserById(topicLike.UserId)
- if userInfo != nil {
- users = append(users, *userInfo)
- }
- }
- return simple.JsonData(users)
-}
-
-// 最新帖子
-func (c *TopicController) GetRecent() *simple.JsonResult {
- topics := services.TopicService.Find(simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(10))
- return simple.JsonData(render.BuildSimpleTopics(topics))
-}
-
-// 用户最近的帖子
-func (c *TopicController) GetUserRecent() *simple.JsonResult {
- userId, err := simple.FormValueInt64(c.Ctx, "userId")
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- topics := services.TopicService.Find(simple.NewSqlCnd().Where("user_id = ? and status = ?",
- userId, model.StatusOk).Desc("id").Limit(10))
- return simple.JsonData(render.BuildSimpleTopics(topics))
-}
-
-// 用户帖子列表
-func (c *TopicController) GetUserTopics() *simple.JsonResult {
- userId, err := simple.FormValueInt64(c.Ctx, "userId")
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- page := simple.FormValueIntDefault(c.Ctx, "page", 1)
-
- topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
- Eq("user_id", userId).
- Eq("status", model.StatusOk).
- Page(page, 20).Desc("id"))
-
- return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
-}
-
-// 帖子列表
-func (c *TopicController) GetTopics() *simple.JsonResult {
- page := simple.FormValueIntDefault(c.Ctx, "page", 1)
-
- topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
- Eq("status", model.StatusOk).
- Page(page, 20).Desc("last_comment_time"))
-
- return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
-}
-
-// 节点帖子列表
-func (c *TopicController) GetNodeTopics() *simple.JsonResult {
- page := simple.FormValueIntDefault(c.Ctx, "page", 1)
- nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
- topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
- Eq("node_id", nodeId).
- Eq("status", model.StatusOk).
- Page(page, 20).Desc("last_comment_time"))
-
- return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
-}
-
-// 标签帖子列表
-func (c *TopicController) GetTagTopics() *simple.JsonResult {
- page := simple.FormValueIntDefault(c.Ctx, "page", 1)
- tagId, err := simple.FormValueInt64(c.Ctx, "tagId")
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- topics, paging := services.TopicService.GetTagTopics(tagId, page)
- return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
-}
-
-// 推荐帖子
-func (c *TopicController) GetRecommendTopics() *simple.JsonResult {
- page := simple.FormValueIntDefault(c.Ctx, "page", 1)
- topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
- Eq("recommend", true).
- Eq("status", model.StatusOk).
- Page(page, 20).Desc("last_comment_time"))
-
- return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
-}
-
-// 收藏
-func (c *TopicController) GetFavoriteBy(topicId int64) *simple.JsonResult {
- user := services.UserTokenService.GetCurrent(c.Ctx)
- if user == nil {
- return simple.JsonError(simple.ErrorNotLogin)
- }
- err := services.FavoriteService.AddTopicFavorite(user.Id, topicId)
- if err != nil {
- return simple.JsonErrorMsg(err.Error())
- }
- return simple.JsonSuccess()
-}
-
-// 推荐
-func (c *TopicController) GetRecommend() *simple.JsonResult {
- topics := cache.TopicCache.GetRecommendTopics()
- if topics == nil || len(topics) == 0 {
- return simple.JsonSuccess()
- } else {
- dest := make([]model.Topic, len(topics))
- perm := rand.Perm(len(topics))
- for i, v := range perm {
- dest[v] = topics[i]
- }
- end := 10
- if end > len(topics) {
- end = len(topics)
- }
- ret := dest[0:end]
- return simple.JsonData(render.BuildSimpleTopics(ret))
- }
-}
-
-// 最新话题
-func (c *TopicController) GetNewest() *simple.JsonResult {
- topics := services.TopicService.Find(simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(6))
- return simple.JsonData(render.BuildSimpleTopics(topics))
-}
+package api
+
+import (
+ "math/rand"
+ "strings"
+
+ "github.com/kataras/iris/v12"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/controllers/render"
+ "bbs-go/model"
+ "bbs-go/services"
+ "bbs-go/services/cache"
+)
+
+type TopicController struct {
+ Ctx iris.Context
+}
+
+// 节点
+func (c *TopicController) GetNodes() *simple.JsonResult {
+ nodes := services.TopicNodeService.GetNodes()
+ return simple.JsonData(render.BuildNodes(nodes))
+}
+
+// 节点信息
+func (c *TopicController) GetNode() *simple.JsonResult {
+ nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
+ node := services.TopicNodeService.Get(nodeId)
+ return simple.JsonData(render.BuildNode(node))
+}
+
+// 发表帖子
+func (c *TopicController) PostCreate() *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
+ title := strings.TrimSpace(simple.FormValue(c.Ctx, "title"))
+ content := strings.TrimSpace(simple.FormValue(c.Ctx, "content"))
+ tags := simple.FormValueStringArray(c.Ctx, "tags")
+ topic, err := services.TopicService.Publish(user.Id, nodeId, tags, title, content)
+ if err != nil {
+ return simple.JsonError(err)
+ }
+ return simple.JsonData(render.BuildSimpleTopic(topic))
+}
+
+// 编辑时获取详情
+func (c *TopicController) GetEditBy(topicId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+
+ topic := services.TopicService.Get(topicId)
+ if topic == nil || topic.Status != model.StatusOk {
+ return simple.JsonErrorMsg("话题不存在或已被删除")
+ }
+ if topic.UserId != user.Id {
+ return simple.JsonErrorMsg("无权限")
+ }
+
+ tags := services.TopicService.GetTopicTags(topicId)
+ var tagNames []string
+ if len(tags) > 0 {
+ for _, tag := range tags {
+ tagNames = append(tagNames, tag.Name)
+ }
+ }
+
+ return simple.NewEmptyRspBuilder().
+ Put("topicId", topic.Id).
+ Put("nodeId", topic.NodeId).
+ Put("title", topic.Title).
+ Put("content", topic.Content).
+ Put("tags", tagNames).
+ JsonResult()
+}
+
+// 编辑帖子
+func (c *TopicController) PostEditBy(topicId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+
+ topic := services.TopicService.Get(topicId)
+ if topic == nil || topic.Status != model.StatusOk {
+ return simple.JsonErrorMsg("话题不存在或已被删除")
+ }
+ if topic.UserId != user.Id {
+ return simple.JsonErrorMsg("无权限")
+ }
+
+ nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
+ title := strings.TrimSpace(simple.FormValue(c.Ctx, "title"))
+ content := strings.TrimSpace(simple.FormValue(c.Ctx, "content"))
+ tags := simple.FormValueStringArray(c.Ctx, "tags")
+
+ err := services.TopicService.Edit(topicId, nodeId, tags, title, content)
+ if err != nil {
+ return simple.JsonError(err)
+ }
+ return simple.JsonData(render.BuildSimpleTopic(topic))
+}
+
+// 删除帖子
+func (c *TopicController) PostDeleteBy(topicId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ topic := services.TopicService.Get(topicId)
+ if topic == nil || topic.Status != model.StatusOk {
+ return simple.JsonSuccess()
+ }
+ if topic.UserId != user.Id {
+ return simple.JsonErrorMsg("无权限")
+ }
+ err := services.TopicService.Delete(topicId)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonSuccess()
+}
+
+// 帖子详情
+func (c *TopicController) GetBy(topicId int64) *simple.JsonResult {
+ topic := services.TopicService.Get(topicId)
+ if topic == nil || topic.Status != model.StatusOk {
+ return simple.JsonErrorMsg("主题不存在")
+ }
+ services.TopicService.IncrViewCount(topicId) // 增加浏览量
+ return simple.JsonData(render.BuildTopic(topic))
+}
+
+// 点赞
+func (c *TopicController) PostLikeBy(topicId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ err := services.UserLikeService.TopicLike(user.Id, topicId)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonSuccess()
+}
+
+// 点赞用户
+func (c *TopicController) GetRecentlikesBy(topicId int64) *simple.JsonResult {
+ likes := services.UserLikeService.Recent(model.EntityTypeTopic, topicId, 10)
+ var users []model.UserInfo
+ for _, like := range likes {
+ userInfo := render.BuildUserById(like.UserId)
+ if userInfo != nil {
+ users = append(users, *userInfo)
+ }
+ }
+ return simple.JsonData(users)
+}
+
+// 最新帖子
+func (c *TopicController) GetRecent() *simple.JsonResult {
+ topics := services.TopicService.Find(simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(10))
+ return simple.JsonData(render.BuildSimpleTopics(topics))
+}
+
+// 用户最近的帖子
+func (c *TopicController) GetUserRecent() *simple.JsonResult {
+ userId, err := simple.FormValueInt64(c.Ctx, "userId")
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ topics := services.TopicService.Find(simple.NewSqlCnd().Where("user_id = ? and status = ?",
+ userId, model.StatusOk).Desc("id").Limit(10))
+ return simple.JsonData(render.BuildSimpleTopics(topics))
+}
+
+// 用户帖子列表
+func (c *TopicController) GetUserTopics() *simple.JsonResult {
+ userId, err := simple.FormValueInt64(c.Ctx, "userId")
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ page := simple.FormValueIntDefault(c.Ctx, "page", 1)
+
+ topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
+ Eq("user_id", userId).
+ Eq("status", model.StatusOk).
+ Page(page, 20).Desc("id"))
+
+ return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
+}
+
+// 帖子列表
+func (c *TopicController) GetTopics() *simple.JsonResult {
+ page := simple.FormValueIntDefault(c.Ctx, "page", 1)
+
+ topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
+ Eq("status", model.StatusOk).
+ Page(page, 20).Desc("last_comment_time"))
+
+ return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
+}
+
+// 节点帖子列表
+func (c *TopicController) GetNodeTopics() *simple.JsonResult {
+ page := simple.FormValueIntDefault(c.Ctx, "page", 1)
+ nodeId := simple.FormValueInt64Default(c.Ctx, "nodeId", 0)
+ topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
+ Eq("node_id", nodeId).
+ Eq("status", model.StatusOk).
+ Page(page, 20).Desc("last_comment_time"))
+
+ return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
+}
+
+// 标签帖子列表
+func (c *TopicController) GetTagTopics() *simple.JsonResult {
+ page := simple.FormValueIntDefault(c.Ctx, "page", 1)
+ tagId, err := simple.FormValueInt64(c.Ctx, "tagId")
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ topics, paging := services.TopicService.GetTagTopics(tagId, page)
+ return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
+}
+
+// 推荐帖子
+func (c *TopicController) GetRecommendTopics() *simple.JsonResult {
+ page := simple.FormValueIntDefault(c.Ctx, "page", 1)
+ topics, paging := services.TopicService.FindPageByCnd(simple.NewSqlCnd().
+ Eq("recommend", true).
+ Eq("status", model.StatusOk).
+ Page(page, 20).Desc("last_comment_time"))
+
+ return simple.JsonPageData(render.BuildSimpleTopics(topics), paging)
+}
+
+// 收藏
+func (c *TopicController) GetFavoriteBy(topicId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ err := services.FavoriteService.AddTopicFavorite(user.Id, topicId)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonSuccess()
+}
+
+// 推荐
+func (c *TopicController) GetRecommend() *simple.JsonResult {
+ topics := cache.TopicCache.GetRecommendTopics()
+ if topics == nil || len(topics) == 0 {
+ return simple.JsonSuccess()
+ } else {
+ dest := make([]model.Topic, len(topics))
+ perm := rand.Perm(len(topics))
+ for i, v := range perm {
+ dest[v] = topics[i]
+ }
+ end := 10
+ if end > len(topics) {
+ end = len(topics)
+ }
+ ret := dest[0:end]
+ return simple.JsonData(render.BuildSimpleTopics(ret))
+ }
+}
+
+// 最新话题
+func (c *TopicController) GetNewest() *simple.JsonResult {
+ topics := services.TopicService.Find(simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(6))
+ return simple.JsonData(render.BuildSimpleTopics(topics))
+}
diff --git a/server/controllers/api/tweet_controller.go b/server/controllers/api/tweet_controller.go
new file mode 100644
index 0000000000000000000000000000000000000000..acbdd668351d3f4223c4f08d3c91d70d47a2800e
--- /dev/null
+++ b/server/controllers/api/tweet_controller.go
@@ -0,0 +1,53 @@
+package api
+
+import (
+ "strconv"
+ "strings"
+
+ "github.com/kataras/iris/v12"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/controllers/render"
+ "bbs-go/services"
+)
+
+type TweetController struct {
+ Ctx iris.Context
+}
+
+func (c *TweetController) PostCreate() *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ content := strings.TrimSpace(simple.FormValue(c.Ctx, "content"))
+ imageList := simple.FormValue(c.Ctx, "imageList")
+ tweets, err := services.TweetService.Publish(user.Id, content, imageList)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonData(render.BuildTweet(tweets))
+}
+
+func (c *TweetController) GetList() *simple.JsonResult {
+ cursor := simple.FormValueInt64Default(c.Ctx, "cursor", 0)
+ tweets, cursor := services.TweetService.GetTweets(cursor)
+ return simple.JsonCursorData(render.BuildTweets(tweets), strconv.FormatInt(cursor, 10))
+}
+
+func (c *TweetController) GetBy(tweetId int64) *simple.JsonResult {
+ tweet := services.TweetService.Get(tweetId)
+ return simple.JsonData(render.BuildTweet(tweet))
+}
+
+func (c *TweetController) PostLikeBy(tweetId int64) *simple.JsonResult {
+ user := services.UserTokenService.GetCurrent(c.Ctx)
+ if user == nil {
+ return simple.JsonError(simple.ErrorNotLogin)
+ }
+ err := services.UserLikeService.TweetLike(user.Id, tweetId)
+ if err != nil {
+ return simple.JsonErrorMsg(err.Error())
+ }
+ return simple.JsonSuccess()
+}
diff --git a/server/controllers/render/render.go b/server/controllers/render/render.go
index 52592c77d0dbff57f12e95ee9de72126136a75a5..c7e672b992ce0aebd95174e5a059979cabcb464b 100644
--- a/server/controllers/render/render.go
+++ b/server/controllers/render/render.go
@@ -1,562 +1,580 @@
-package render
-
-import (
- "html/template"
- "strconv"
- "strings"
-
- "github.com/sirupsen/logrus"
- "github.com/tidwall/gjson"
-
- "github.com/PuerkitoBio/goquery"
- "github.com/mlogclub/simple"
-
- "bbs-go/common"
- "bbs-go/common/avatar"
- "bbs-go/common/urls"
- "bbs-go/model"
- "bbs-go/services"
- "bbs-go/services/cache"
-)
-
-func BuildUserDefaultIfNull(id int64) *model.UserInfo {
- user := cache.UserCache.Get(id)
- if user == nil {
- user = &model.User{}
- user.Id = id
- user.Username = simple.SqlNullString(strconv.FormatInt(id, 10))
- user.Avatar = avatar.DefaultAvatar
- user.CreateTime = simple.NowTimestamp()
- }
- return BuildUser(user)
-}
-
-func BuildUserById(id int64) *model.UserInfo {
- user := cache.UserCache.Get(id)
- return BuildUser(user)
-}
-
-func BuildUser(user *model.User) *model.UserInfo {
- if user == nil {
- return nil
- }
- a := user.Avatar
- if len(a) == 0 {
- a = avatar.DefaultAvatar
- }
- roles := strings.Split(user.Roles, ",")
- ret := &model.UserInfo{
- Id: user.Id,
- Username: user.Username.String,
- Nickname: user.Nickname,
- Avatar: a,
- Email: user.Email.String,
- Type: user.Type,
- Roles: roles,
- HomePage: user.HomePage,
- Description: user.Description,
- TopicCount: user.TopicCount,
- CommentCount: user.CommentCount,
- PasswordSet: len(user.Password) > 0,
- Status: user.Status,
- CreateTime: user.CreateTime,
- }
- if user.Status == model.StatusDeleted {
- ret.Username = "blacklist"
- ret.Nickname = "黑名单用户"
- ret.Avatar = avatar.DefaultAvatar
- ret.Email = ""
- ret.HomePage = ""
- ret.Description = ""
- } else {
- ret.Score = cache.UserCache.GetScore(user.Id)
- }
- return ret
-}
-
-func BuildUsers(users []model.User) []model.UserInfo {
- if len(users) == 0 {
- return nil
- }
- var responses []model.UserInfo
- for _, user := range users {
- item := BuildUser(&user)
- if item != nil {
- responses = append(responses, *item)
- }
- }
- return responses
-}
-
-func BuildArticle(article *model.Article) *model.ArticleResponse {
- if article == nil {
- return nil
- }
-
- rsp := &model.ArticleResponse{}
- rsp.ArticleId = article.Id
- rsp.Title = article.Title
- rsp.Summary = article.Summary
- rsp.Share = article.Share
- rsp.SourceUrl = article.SourceUrl
- rsp.ViewCount = article.ViewCount
- rsp.CreateTime = article.CreateTime
-
- rsp.User = BuildUserDefaultIfNull(article.UserId)
-
- tagIds := cache.ArticleTagCache.Get(article.Id)
- tags := cache.TagCache.GetList(tagIds)
- rsp.Tags = BuildTags(tags)
-
- if article.ContentType == model.ContentTypeMarkdown {
- mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content)
- rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
- rsp.Toc = template.HTML(mr.TocHtml)
- if len(rsp.Summary) == 0 {
- rsp.Summary = mr.SummaryText
- }
- } else {
- rsp.Content = template.HTML(BuildHtmlContent(article.Content))
- if len(rsp.Summary) == 0 {
- rsp.Summary = simple.GetSummary(article.Content, 256)
- }
- }
-
- return rsp
-}
-
-func BuildArticles(articles []model.Article) []model.ArticleResponse {
- if articles == nil || len(articles) == 0 {
- return nil
- }
- var responses []model.ArticleResponse
- for _, article := range articles {
- responses = append(responses, *BuildArticle(&article))
- }
- return responses
-}
-
-func BuildSimpleArticle(article *model.Article) *model.ArticleSimpleResponse {
- if article == nil {
- return nil
- }
-
- rsp := &model.ArticleSimpleResponse{}
- rsp.ArticleId = article.Id
- rsp.Title = article.Title
- rsp.Summary = article.Summary
- rsp.Share = article.Share
- rsp.SourceUrl = article.SourceUrl
- rsp.ViewCount = article.ViewCount
- rsp.CreateTime = article.CreateTime
-
- rsp.User = BuildUserDefaultIfNull(article.UserId)
-
- tagIds := cache.ArticleTagCache.Get(article.Id)
- tags := cache.TagCache.GetList(tagIds)
- rsp.Tags = BuildTags(tags)
-
- if article.ContentType == model.ContentTypeMarkdown {
- if len(rsp.Summary) == 0 {
- mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content)
- rsp.Summary = mr.SummaryText
- }
- } else {
- if len(rsp.Summary) == 0 {
- rsp.Summary = simple.GetSummary(simple.GetHtmlText(article.Content), 256)
- }
- }
-
- return rsp
-}
-
-func BuildSimpleArticles(articles []model.Article) []model.ArticleSimpleResponse {
- if articles == nil || len(articles) == 0 {
- return nil
- }
- var responses []model.ArticleSimpleResponse
- for _, article := range articles {
- responses = append(responses, *BuildSimpleArticle(&article))
- }
- return responses
-}
-
-func BuildNode(node *model.TopicNode) *model.NodeResponse {
- if node == nil {
- return nil
- }
- return &model.NodeResponse{
- NodeId: node.Id,
- Name: node.Name,
- Description: node.Description,
- }
-}
-
-func BuildNodes(nodes []model.TopicNode) []model.NodeResponse {
- if len(nodes) == 0 {
- return nil
- }
- var ret []model.NodeResponse
- for _, node := range nodes {
- ret = append(ret, *BuildNode(&node))
- }
- return ret
-}
-
-func BuildTopic(topic *model.Topic) *model.TopicResponse {
- if topic == nil {
- return nil
- }
-
- rsp := &model.TopicResponse{}
-
- rsp.TopicId = topic.Id
- rsp.Type = topic.Type
- rsp.Title = topic.Title
- rsp.User = BuildUserDefaultIfNull(topic.UserId)
- rsp.LastCommentTime = topic.LastCommentTime
- rsp.CreateTime = topic.CreateTime
- rsp.ViewCount = topic.ViewCount
- rsp.CommentCount = topic.CommentCount
- rsp.LikeCount = topic.LikeCount
-
- if topic.NodeId > 0 {
- node := services.TopicNodeService.Get(topic.NodeId)
- rsp.Node = BuildNode(node)
- }
-
- tags := services.TopicService.GetTopicTags(topic.Id)
- rsp.Tags = BuildTags(tags)
-
- mr := simple.NewMd(simple.MdWithTOC()).Run(topic.Content)
- rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
- rsp.Toc = template.HTML(mr.TocHtml)
-
- if len(topic.ImageList) > 0 {
- if err := simple.ParseJson(topic.ImageList, &rsp.ImageList); err != nil {
- logrus.Error(err)
- }
- }
-
- return rsp
-}
-
-func BuildSimpleTopic(topic *model.Topic) *model.TopicSimpleResponse {
- if topic == nil {
- return nil
- }
-
- rsp := &model.TopicSimpleResponse{}
-
- rsp.TopicId = topic.Id
- rsp.Type = topic.Type
- rsp.Title = topic.Title
- rsp.User = BuildUserDefaultIfNull(topic.UserId)
- rsp.LastCommentTime = topic.LastCommentTime
- rsp.CreateTime = topic.CreateTime
- rsp.ViewCount = topic.ViewCount
- rsp.CommentCount = topic.CommentCount
- rsp.LikeCount = topic.LikeCount
-
- if len(topic.ImageList) > 0 {
- if err := simple.ParseJson(topic.ImageList, &rsp.ImageList); err != nil {
- logrus.Error(err)
- }
- }
-
- if topic.NodeId > 0 {
- node := services.TopicNodeService.Get(topic.NodeId)
- rsp.Node = BuildNode(node)
- }
-
- tags := services.TopicService.GetTopicTags(topic.Id)
- rsp.Tags = BuildTags(tags)
- return rsp
-}
-
-func BuildSimpleTopics(topics []model.Topic) []model.TopicSimpleResponse {
- if topics == nil || len(topics) == 0 {
- return nil
- }
- var responses []model.TopicSimpleResponse
- for _, topic := range topics {
- responses = append(responses, *BuildSimpleTopic(&topic))
- }
- return responses
-}
-
-func BuildProject(project *model.Project) *model.ProjectResponse {
- if project == nil {
- return nil
- }
- rsp := &model.ProjectResponse{}
- rsp.ProjectId = project.Id
- rsp.User = BuildUserDefaultIfNull(project.UserId)
- rsp.Name = project.Name
- rsp.Title = project.Title
- rsp.Logo = project.Logo
- rsp.Url = project.Url
- rsp.Url = project.Url
- rsp.DocUrl = project.DocUrl
- rsp.CreateTime = project.CreateTime
-
- if project.ContentType == model.ContentTypeHtml {
- rsp.Content = template.HTML(BuildHtmlContent(project.Content))
- rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256)
- } else {
- mr := simple.NewMd().Run(project.Content)
- rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
- rsp.Summary = mr.SummaryText
- }
-
- return rsp
-}
-
-func BuildSimpleProjects(projects []model.Project) []model.ProjectSimpleResponse {
- if projects == nil || len(projects) == 0 {
- return nil
- }
- var responses []model.ProjectSimpleResponse
- for _, project := range projects {
- responses = append(responses, *BuildSimpleProject(&project))
- }
- return responses
-}
-
-func BuildSimpleProject(project *model.Project) *model.ProjectSimpleResponse {
- if project == nil {
- return nil
- }
- rsp := &model.ProjectSimpleResponse{}
- rsp.ProjectId = project.Id
- rsp.User = BuildUserDefaultIfNull(project.UserId)
- rsp.Name = project.Name
- rsp.Title = project.Title
- rsp.Logo = project.Logo
- rsp.Url = project.Url
- rsp.DocUrl = project.DocUrl
- rsp.DownloadUrl = project.DownloadUrl
- rsp.CreateTime = project.CreateTime
-
- if project.ContentType == model.ContentTypeHtml {
- rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256)
- } else {
- rsp.Summary = common.GetMarkdownSummary(project.Content)
- }
-
- return rsp
-}
-
-func BuildComments(comments []model.Comment) []model.CommentResponse {
- var ret []model.CommentResponse
- for _, comment := range comments {
- ret = append(ret, *BuildComment(comment))
- }
- return ret
-}
-
-func BuildComment(comment model.Comment) *model.CommentResponse {
- return _buildComment(&comment, true)
-}
-
-func _buildComment(comment *model.Comment, buildQuote bool) *model.CommentResponse {
- if comment == nil {
- return nil
- }
-
- ret := &model.CommentResponse{
- CommentId: comment.Id,
- User: BuildUserDefaultIfNull(comment.UserId),
- EntityType: comment.EntityType,
- EntityId: comment.EntityId,
- QuoteId: comment.QuoteId,
- Status: comment.Status,
- CreateTime: comment.CreateTime,
- }
-
- if comment.ContentType == model.ContentTypeMarkdown {
- markdownResult := simple.NewMd().Run(comment.Content)
- ret.Content = template.HTML(BuildHtmlContent(markdownResult.ContentHtml))
- } else {
- ret.Content = template.HTML(BuildHtmlContent(comment.Content))
- }
-
- if buildQuote && comment.QuoteId > 0 {
- quote := _buildComment(services.CommentService.Get(comment.QuoteId), false)
- if quote != nil {
- ret.Quote = quote
- ret.QuoteContent = template.HTML(quote.User.Nickname+":") + quote.Content
- }
- }
- return ret
-}
-
-func BuildTag(tag *model.Tag) *model.TagResponse {
- if tag == nil {
- return nil
- }
- return &model.TagResponse{TagId: tag.Id, TagName: tag.Name}
-}
-
-func BuildTags(tags []model.Tag) *[]model.TagResponse {
- if len(tags) == 0 {
- return nil
- }
- var responses []model.TagResponse
- for _, tag := range tags {
- responses = append(responses, *BuildTag(&tag))
- }
- return &responses
-}
-
-func BuildFavorite(favorite *model.Favorite) *model.FavoriteResponse {
- rsp := &model.FavoriteResponse{}
- rsp.FavoriteId = favorite.Id
- rsp.EntityType = favorite.EntityType
- rsp.CreateTime = favorite.CreateTime
-
- if favorite.EntityType == model.EntityTypeArticle {
- article := services.ArticleService.Get(favorite.EntityId)
- if article == nil || article.Status != model.StatusOk {
- rsp.Deleted = true
- } else {
- rsp.Url = urls.ArticleUrl(article.Id)
- rsp.User = BuildUserById(article.UserId)
- rsp.Title = article.Title
- if article.ContentType == model.ContentTypeMarkdown {
- rsp.Content = common.GetMarkdownSummary(article.Content)
- } else {
- doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content))
- if err == nil {
- text := doc.Text()
- rsp.Content = simple.GetSummary(text, 256)
- }
- }
- }
- } else {
- topic := services.TopicService.Get(favorite.EntityId)
- if topic == nil || topic.Status != model.StatusOk {
- rsp.Deleted = true
- } else {
- rsp.Url = urls.TopicUrl(topic.Id)
- rsp.User = BuildUserById(topic.UserId)
- rsp.Title = topic.Title
- rsp.Content = common.GetMarkdownSummary(topic.Content)
- }
- }
- return rsp
-}
-
-func BuildFavorites(favorites []model.Favorite) []model.FavoriteResponse {
- if favorites == nil || len(favorites) == 0 {
- return nil
- }
- var responses []model.FavoriteResponse
- for _, favorite := range favorites {
- responses = append(responses, *BuildFavorite(&favorite))
- }
- return responses
-}
-
-func BuildMessage(message *model.Message) *model.MessageResponse {
- if message == nil {
- return nil
- }
-
- detailUrl := ""
- if message.Type == model.MsgTypeComment {
- entityType := gjson.Get(message.ExtraData, "entityType")
- entityId := gjson.Get(message.ExtraData, "entityId")
- if entityType.String() == model.EntityTypeArticle {
- detailUrl = urls.ArticleUrl(entityId.Int())
- } else if entityType.String() == model.EntityTypeTopic {
- detailUrl = urls.TopicUrl(entityId.Int())
- }
- }
- from := BuildUserDefaultIfNull(message.FromId)
- if message.FromId <= 0 {
- from.Nickname = "系统通知"
- from.Avatar = avatar.DefaultAvatar
- }
- return &model.MessageResponse{
- MessageId: message.Id,
- From: from,
- UserId: message.UserId,
- Content: message.Content,
- QuoteContent: message.QuoteContent,
- Type: message.Type,
- DetailUrl: detailUrl,
- ExtraData: message.ExtraData,
- Status: message.Status,
- CreateTime: message.CreateTime,
- }
-}
-
-func BuildMessages(messages []model.Message) []model.MessageResponse {
- if len(messages) == 0 {
- return nil
- }
- var responses []model.MessageResponse
- for _, message := range messages {
- responses = append(responses, *BuildMessage(&message))
- }
- return responses
-}
-
-func BuildHtmlContent(htmlContent string) string {
- doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
- if err != nil {
- return htmlContent
- }
-
- doc.Find("a").Each(func(i int, selection *goquery.Selection) {
- href := selection.AttrOr("href", "")
-
- if len(href) == 0 {
- return
- }
-
- // 不是内部链接
- if !urls.IsInternalUrl(href) {
- selection.SetAttr("target", "_blank")
- selection.SetAttr("rel", "external nofollow") // 标记站外链接,搜索引擎爬虫不传递权重值
-
- config := services.SysConfigService.GetConfig()
- if config.UrlRedirect { // 开启非内部链接跳转
- newHref := simple.ParseUrl(urls.AbsUrl("/redirect")).AddQuery("url", href).BuildStr()
- selection.SetAttr("href", newHref)
- }
- }
-
- // 如果是锚链接
- if urls.IsAnchor(href) {
- selection.ReplaceWithHtml(selection.Text())
- }
-
- // 如果a标签没有title,那么设置title
- title := selection.AttrOr("title", "")
- if len(title) == 0 {
- selection.SetAttr("title", selection.Text())
- }
- })
-
- // 处理图片
- doc.Find("img").Each(func(i int, selection *goquery.Selection) {
- src := selection.AttrOr("src", "")
- // 处理第三方图片
- if strings.Contains(src, "qpic.cn") {
- src = simple.ParseUrl("/api/img/proxy").AddQuery("url", src).BuildStr()
- // selection.SetAttr("src", src)
- }
-
- // 处理lazyload
- selection.SetAttr("data-src", src)
- selection.RemoveAttr("src")
- })
-
- html, err := doc.Find("body").Html()
- if err != nil {
- return htmlContent
- }
- return html
-}
+package render
+
+import (
+ "html/template"
+ "strconv"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+ "github.com/tidwall/gjson"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/common"
+ "bbs-go/common/avatar"
+ "bbs-go/common/urls"
+ "bbs-go/model"
+ "bbs-go/services"
+ "bbs-go/services/cache"
+)
+
+func BuildUserDefaultIfNull(id int64) *model.UserInfo {
+ user := cache.UserCache.Get(id)
+ if user == nil {
+ user = &model.User{}
+ user.Id = id
+ user.Username = simple.SqlNullString(strconv.FormatInt(id, 10))
+ user.Avatar = avatar.DefaultAvatar
+ user.CreateTime = simple.NowTimestamp()
+ }
+ return BuildUser(user)
+}
+
+func BuildUserById(id int64) *model.UserInfo {
+ user := cache.UserCache.Get(id)
+ return BuildUser(user)
+}
+
+func BuildUser(user *model.User) *model.UserInfo {
+ if user == nil {
+ return nil
+ }
+ a := user.Avatar
+ if len(a) == 0 {
+ a = avatar.DefaultAvatar
+ }
+ roles := strings.Split(user.Roles, ",")
+ ret := &model.UserInfo{
+ Id: user.Id,
+ Username: user.Username.String,
+ Nickname: user.Nickname,
+ Avatar: a,
+ Email: user.Email.String,
+ Type: user.Type,
+ Roles: roles,
+ HomePage: user.HomePage,
+ Description: user.Description,
+ TopicCount: user.TopicCount,
+ CommentCount: user.CommentCount,
+ PasswordSet: len(user.Password) > 0,
+ Status: user.Status,
+ CreateTime: user.CreateTime,
+ }
+ if len(ret.Description) == 0 {
+ ret.Description = "这家伙很懒,什么都没留下"
+ }
+ if user.Status == model.StatusDeleted {
+ ret.Username = "blacklist"
+ ret.Nickname = "黑名单用户"
+ ret.Avatar = avatar.DefaultAvatar
+ ret.Email = ""
+ ret.HomePage = ""
+ ret.Description = ""
+ } else {
+ ret.Score = cache.UserCache.GetScore(user.Id)
+ }
+ return ret
+}
+
+func BuildUsers(users []model.User) []model.UserInfo {
+ if len(users) == 0 {
+ return nil
+ }
+ var responses []model.UserInfo
+ for _, user := range users {
+ item := BuildUser(&user)
+ if item != nil {
+ responses = append(responses, *item)
+ }
+ }
+ return responses
+}
+
+func BuildArticle(article *model.Article) *model.ArticleResponse {
+ if article == nil {
+ return nil
+ }
+
+ rsp := &model.ArticleResponse{}
+ rsp.ArticleId = article.Id
+ rsp.Title = article.Title
+ rsp.Summary = article.Summary
+ rsp.Share = article.Share
+ rsp.SourceUrl = article.SourceUrl
+ rsp.ViewCount = article.ViewCount
+ rsp.CreateTime = article.CreateTime
+
+ rsp.User = BuildUserDefaultIfNull(article.UserId)
+
+ tagIds := cache.ArticleTagCache.Get(article.Id)
+ tags := cache.TagCache.GetList(tagIds)
+ rsp.Tags = BuildTags(tags)
+
+ if article.ContentType == model.ContentTypeMarkdown {
+ mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content)
+ rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
+ rsp.Toc = template.HTML(mr.TocHtml)
+ if len(rsp.Summary) == 0 {
+ rsp.Summary = mr.SummaryText
+ }
+ } else {
+ rsp.Content = template.HTML(BuildHtmlContent(article.Content))
+ if len(rsp.Summary) == 0 {
+ rsp.Summary = simple.GetSummary(article.Content, 256)
+ }
+ }
+
+ return rsp
+}
+
+func BuildArticles(articles []model.Article) []model.ArticleResponse {
+ if articles == nil || len(articles) == 0 {
+ return nil
+ }
+ var responses []model.ArticleResponse
+ for _, article := range articles {
+ responses = append(responses, *BuildArticle(&article))
+ }
+ return responses
+}
+
+func BuildSimpleArticle(article *model.Article) *model.ArticleSimpleResponse {
+ if article == nil {
+ return nil
+ }
+
+ rsp := &model.ArticleSimpleResponse{}
+ rsp.ArticleId = article.Id
+ rsp.Title = article.Title
+ rsp.Summary = article.Summary
+ rsp.Share = article.Share
+ rsp.SourceUrl = article.SourceUrl
+ rsp.ViewCount = article.ViewCount
+ rsp.CreateTime = article.CreateTime
+
+ rsp.User = BuildUserDefaultIfNull(article.UserId)
+
+ tagIds := cache.ArticleTagCache.Get(article.Id)
+ tags := cache.TagCache.GetList(tagIds)
+ rsp.Tags = BuildTags(tags)
+
+ if article.ContentType == model.ContentTypeMarkdown {
+ if len(rsp.Summary) == 0 {
+ mr := simple.NewMd(simple.MdWithTOC()).Run(article.Content)
+ rsp.Summary = mr.SummaryText
+ }
+ } else {
+ if len(rsp.Summary) == 0 {
+ rsp.Summary = simple.GetSummary(simple.GetHtmlText(article.Content), 256)
+ }
+ }
+
+ return rsp
+}
+
+func BuildSimpleArticles(articles []model.Article) []model.ArticleSimpleResponse {
+ if articles == nil || len(articles) == 0 {
+ return nil
+ }
+ var responses []model.ArticleSimpleResponse
+ for _, article := range articles {
+ responses = append(responses, *BuildSimpleArticle(&article))
+ }
+ return responses
+}
+
+func BuildNode(node *model.TopicNode) *model.NodeResponse {
+ if node == nil {
+ return nil
+ }
+ return &model.NodeResponse{
+ NodeId: node.Id,
+ Name: node.Name,
+ Description: node.Description,
+ }
+}
+
+func BuildNodes(nodes []model.TopicNode) []model.NodeResponse {
+ if len(nodes) == 0 {
+ return nil
+ }
+ var ret []model.NodeResponse
+ for _, node := range nodes {
+ ret = append(ret, *BuildNode(&node))
+ }
+ return ret
+}
+
+func BuildTopic(topic *model.Topic) *model.TopicResponse {
+ if topic == nil {
+ return nil
+ }
+
+ rsp := &model.TopicResponse{}
+
+ rsp.TopicId = topic.Id
+ rsp.Title = topic.Title
+ rsp.User = BuildUserDefaultIfNull(topic.UserId)
+ rsp.LastCommentTime = topic.LastCommentTime
+ rsp.CreateTime = topic.CreateTime
+ rsp.ViewCount = topic.ViewCount
+ rsp.CommentCount = topic.CommentCount
+ rsp.LikeCount = topic.LikeCount
+
+ if topic.NodeId > 0 {
+ node := services.TopicNodeService.Get(topic.NodeId)
+ rsp.Node = BuildNode(node)
+ }
+
+ tags := services.TopicService.GetTopicTags(topic.Id)
+ rsp.Tags = BuildTags(tags)
+
+ mr := simple.NewMd(simple.MdWithTOC()).Run(topic.Content)
+ rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
+ rsp.Toc = template.HTML(mr.TocHtml)
+
+ return rsp
+}
+
+func BuildSimpleTopic(topic *model.Topic) *model.TopicSimpleResponse {
+ if topic == nil {
+ return nil
+ }
+
+ rsp := &model.TopicSimpleResponse{}
+
+ rsp.TopicId = topic.Id
+ rsp.Title = topic.Title
+ rsp.User = BuildUserDefaultIfNull(topic.UserId)
+ rsp.LastCommentTime = topic.LastCommentTime
+ rsp.CreateTime = topic.CreateTime
+ rsp.ViewCount = topic.ViewCount
+ rsp.CommentCount = topic.CommentCount
+ rsp.LikeCount = topic.LikeCount
+
+ if topic.NodeId > 0 {
+ node := services.TopicNodeService.Get(topic.NodeId)
+ rsp.Node = BuildNode(node)
+ }
+
+ tags := services.TopicService.GetTopicTags(topic.Id)
+ rsp.Tags = BuildTags(tags)
+ return rsp
+}
+
+func BuildSimpleTopics(topics []model.Topic) []model.TopicSimpleResponse {
+ if topics == nil || len(topics) == 0 {
+ return nil
+ }
+ var responses []model.TopicSimpleResponse
+ for _, topic := range topics {
+ responses = append(responses, *BuildSimpleTopic(&topic))
+ }
+ return responses
+}
+
+func BuildTweet(tweet *model.Tweet) *model.TweetResponse {
+ if tweet == nil {
+ return nil
+ }
+
+ rsp := &model.TweetResponse{
+ TweetId: tweet.Id,
+ User: BuildUserDefaultIfNull(tweet.UserId),
+ Content: tweet.Content,
+ CommentCount: tweet.CommentCount,
+ LikeCount: tweet.LikeCount,
+ CreateTime: tweet.CreateTime,
+ }
+ if len(tweet.ImageList) > 0 {
+ if err := simple.ParseJson(tweet.ImageList, &rsp.ImageList); err != nil {
+ logrus.Error(err)
+ }
+ }
+ return rsp
+}
+
+func BuildTweets(tweets []model.Tweet) []model.TweetResponse {
+ var ret []model.TweetResponse
+ for _, tweet := range tweets {
+ ret = append(ret, *BuildTweet(&tweet))
+ }
+ return ret
+}
+
+func BuildProject(project *model.Project) *model.ProjectResponse {
+ if project == nil {
+ return nil
+ }
+ rsp := &model.ProjectResponse{}
+ rsp.ProjectId = project.Id
+ rsp.User = BuildUserDefaultIfNull(project.UserId)
+ rsp.Name = project.Name
+ rsp.Title = project.Title
+ rsp.Logo = project.Logo
+ rsp.Url = project.Url
+ rsp.Url = project.Url
+ rsp.DocUrl = project.DocUrl
+ rsp.CreateTime = project.CreateTime
+
+ if project.ContentType == model.ContentTypeHtml {
+ rsp.Content = template.HTML(BuildHtmlContent(project.Content))
+ rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256)
+ } else {
+ mr := simple.NewMd().Run(project.Content)
+ rsp.Content = template.HTML(BuildHtmlContent(mr.ContentHtml))
+ rsp.Summary = mr.SummaryText
+ }
+
+ return rsp
+}
+
+func BuildSimpleProjects(projects []model.Project) []model.ProjectSimpleResponse {
+ if projects == nil || len(projects) == 0 {
+ return nil
+ }
+ var responses []model.ProjectSimpleResponse
+ for _, project := range projects {
+ responses = append(responses, *BuildSimpleProject(&project))
+ }
+ return responses
+}
+
+func BuildSimpleProject(project *model.Project) *model.ProjectSimpleResponse {
+ if project == nil {
+ return nil
+ }
+ rsp := &model.ProjectSimpleResponse{}
+ rsp.ProjectId = project.Id
+ rsp.User = BuildUserDefaultIfNull(project.UserId)
+ rsp.Name = project.Name
+ rsp.Title = project.Title
+ rsp.Logo = project.Logo
+ rsp.Url = project.Url
+ rsp.DocUrl = project.DocUrl
+ rsp.DownloadUrl = project.DownloadUrl
+ rsp.CreateTime = project.CreateTime
+
+ if project.ContentType == model.ContentTypeHtml {
+ rsp.Summary = simple.GetSummary(simple.GetHtmlText(project.Content), 256)
+ } else {
+ rsp.Summary = common.GetMarkdownSummary(project.Content)
+ }
+
+ return rsp
+}
+
+func BuildComments(comments []model.Comment) []model.CommentResponse {
+ var ret []model.CommentResponse
+ for _, comment := range comments {
+ ret = append(ret, *BuildComment(comment))
+ }
+ return ret
+}
+
+func BuildComment(comment model.Comment) *model.CommentResponse {
+ return _buildComment(&comment, true)
+}
+
+func _buildComment(comment *model.Comment, buildQuote bool) *model.CommentResponse {
+ if comment == nil {
+ return nil
+ }
+
+ ret := &model.CommentResponse{
+ CommentId: comment.Id,
+ User: BuildUserDefaultIfNull(comment.UserId),
+ EntityType: comment.EntityType,
+ EntityId: comment.EntityId,
+ QuoteId: comment.QuoteId,
+ Status: comment.Status,
+ CreateTime: comment.CreateTime,
+ }
+
+ if comment.ContentType == model.ContentTypeMarkdown {
+ markdownResult := simple.NewMd().Run(comment.Content)
+ ret.Content = template.HTML(BuildHtmlContent(markdownResult.ContentHtml))
+ } else {
+ ret.Content = template.HTML(BuildHtmlContent(comment.Content))
+ }
+
+ if buildQuote && comment.QuoteId > 0 {
+ quote := _buildComment(services.CommentService.Get(comment.QuoteId), false)
+ if quote != nil {
+ ret.Quote = quote
+ ret.QuoteContent = template.HTML(quote.User.Nickname+":") + quote.Content
+ }
+ }
+ return ret
+}
+
+func BuildTag(tag *model.Tag) *model.TagResponse {
+ if tag == nil {
+ return nil
+ }
+ return &model.TagResponse{TagId: tag.Id, TagName: tag.Name}
+}
+
+func BuildTags(tags []model.Tag) *[]model.TagResponse {
+ if len(tags) == 0 {
+ return nil
+ }
+ var responses []model.TagResponse
+ for _, tag := range tags {
+ responses = append(responses, *BuildTag(&tag))
+ }
+ return &responses
+}
+
+func BuildFavorite(favorite *model.Favorite) *model.FavoriteResponse {
+ rsp := &model.FavoriteResponse{}
+ rsp.FavoriteId = favorite.Id
+ rsp.EntityType = favorite.EntityType
+ rsp.CreateTime = favorite.CreateTime
+
+ if favorite.EntityType == model.EntityTypeArticle {
+ article := services.ArticleService.Get(favorite.EntityId)
+ if article == nil || article.Status != model.StatusOk {
+ rsp.Deleted = true
+ } else {
+ rsp.Url = urls.ArticleUrl(article.Id)
+ rsp.User = BuildUserById(article.UserId)
+ rsp.Title = article.Title
+ if article.ContentType == model.ContentTypeMarkdown {
+ rsp.Content = common.GetMarkdownSummary(article.Content)
+ } else {
+ doc, err := goquery.NewDocumentFromReader(strings.NewReader(article.Content))
+ if err == nil {
+ text := doc.Text()
+ rsp.Content = simple.GetSummary(text, 256)
+ }
+ }
+ }
+ } else {
+ topic := services.TopicService.Get(favorite.EntityId)
+ if topic == nil || topic.Status != model.StatusOk {
+ rsp.Deleted = true
+ } else {
+ rsp.Url = urls.TopicUrl(topic.Id)
+ rsp.User = BuildUserById(topic.UserId)
+ rsp.Title = topic.Title
+ rsp.Content = common.GetMarkdownSummary(topic.Content)
+ }
+ }
+ return rsp
+}
+
+func BuildFavorites(favorites []model.Favorite) []model.FavoriteResponse {
+ if favorites == nil || len(favorites) == 0 {
+ return nil
+ }
+ var responses []model.FavoriteResponse
+ for _, favorite := range favorites {
+ responses = append(responses, *BuildFavorite(&favorite))
+ }
+ return responses
+}
+
+func BuildMessage(message *model.Message) *model.MessageResponse {
+ if message == nil {
+ return nil
+ }
+
+ detailUrl := ""
+ if message.Type == model.MsgTypeComment {
+ entityType := gjson.Get(message.ExtraData, "entityType")
+ entityId := gjson.Get(message.ExtraData, "entityId")
+ if entityType.String() == model.EntityTypeArticle {
+ detailUrl = urls.ArticleUrl(entityId.Int())
+ } else if entityType.String() == model.EntityTypeTopic {
+ detailUrl = urls.TopicUrl(entityId.Int())
+ }
+ }
+ from := BuildUserDefaultIfNull(message.FromId)
+ if message.FromId <= 0 {
+ from.Nickname = "系统通知"
+ from.Avatar = avatar.DefaultAvatar
+ }
+ return &model.MessageResponse{
+ MessageId: message.Id,
+ From: from,
+ UserId: message.UserId,
+ Content: message.Content,
+ QuoteContent: message.QuoteContent,
+ Type: message.Type,
+ DetailUrl: detailUrl,
+ ExtraData: message.ExtraData,
+ Status: message.Status,
+ CreateTime: message.CreateTime,
+ }
+}
+
+func BuildMessages(messages []model.Message) []model.MessageResponse {
+ if len(messages) == 0 {
+ return nil
+ }
+ var responses []model.MessageResponse
+ for _, message := range messages {
+ responses = append(responses, *BuildMessage(&message))
+ }
+ return responses
+}
+
+func BuildHtmlContent(htmlContent string) string {
+ doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
+ if err != nil {
+ return htmlContent
+ }
+
+ doc.Find("a").Each(func(i int, selection *goquery.Selection) {
+ href := selection.AttrOr("href", "")
+
+ if len(href) == 0 {
+ return
+ }
+
+ // 不是内部链接
+ if !urls.IsInternalUrl(href) {
+ selection.SetAttr("target", "_blank")
+ selection.SetAttr("rel", "external nofollow") // 标记站外链接,搜索引擎爬虫不传递权重值
+
+ config := services.SysConfigService.GetConfig()
+ if config.UrlRedirect { // 开启非内部链接跳转
+ newHref := simple.ParseUrl(urls.AbsUrl("/redirect")).AddQuery("url", href).BuildStr()
+ selection.SetAttr("href", newHref)
+ }
+ }
+
+ // 如果是锚链接
+ if urls.IsAnchor(href) {
+ selection.ReplaceWithHtml(selection.Text())
+ }
+
+ // 如果a标签没有title,那么设置title
+ title := selection.AttrOr("title", "")
+ if len(title) == 0 {
+ selection.SetAttr("title", selection.Text())
+ }
+ })
+
+ // 处理图片
+ doc.Find("img").Each(func(i int, selection *goquery.Selection) {
+ src := selection.AttrOr("src", "")
+ // 处理第三方图片
+ if strings.Contains(src, "qpic.cn") {
+ src = simple.ParseUrl("/api/img/proxy").AddQuery("url", src).BuildStr()
+ // selection.SetAttr("src", src)
+ }
+
+ // 处理lazyload
+ selection.SetAttr("data-src", src)
+ selection.RemoveAttr("src")
+ })
+
+ html, err := doc.Find("body").Html()
+ if err != nil {
+ return htmlContent
+ }
+ return html
+}
diff --git a/server/model/models.go b/server/model/models.go
index 51dca7f290d72ffa1a2400d204d7e062352770ca..4e8f1d56f4a15e748686e74d974d343bfe64464b 100644
--- a/server/model/models.go
+++ b/server/model/models.go
@@ -5,9 +5,9 @@ import (
)
var Models = []interface{}{
- &User{}, &UserToken{}, &Tag{}, &Article{}, &ArticleTag{}, &Comment{}, &Favorite{},
- &Topic{}, &TopicNode{}, &TopicTag{}, &TopicLike{}, &Message{}, &SysConfig{}, &Project{}, &Link{},
- &ThirdAccount{}, &Sitemap{}, &UserScore{}, &UserScoreLog{},
+ &User{}, &UserToken{}, &Tag{}, &Article{}, &ArticleTag{}, &Comment{}, &Favorite{}, &Topic{}, &TopicNode{},
+ &TopicTag{}, &UserLike{}, &Tweet{}, &Message{}, &SysConfig{}, &Project{}, &Link{}, &ThirdAccount{}, &Sitemap{},
+ &UserScore{}, &UserScoreLog{},
}
type Model struct {
@@ -28,6 +28,7 @@ const (
EntityTypeArticle = "article"
EntityTypeTopic = "topic"
EntityTypeComment = "comment"
+ EntityTypeTweet = "tweet"
MsgStatusUnread = 0 // 消息未读
MsgStatusReaded = 1 // 消息已读
@@ -39,9 +40,6 @@ const (
ScoreTypeIncr = 0 // 积分+
ScoreTypeDecr = 1 // 积分-
-
- TopicTypeNormal = 0 // 普通帖子
- TopicTypeTwitter = 1 // 推文
)
type User struct {
@@ -153,12 +151,10 @@ type TopicNode struct {
// 话题节点
type Topic struct {
Model
- Type int `gorm:"not null;index:idx_topic_type" json:"type" form:"type"` // 类型
NodeId int64 `gorm:"not null;index:idx_node_id;" json:"nodeId" form:"nodeId"` // 节点编号
UserId int64 `gorm:"not null;index:idx_topic_user_id;" json:"userId" form:"userId"` // 用户
Title string `gorm:"size:128" json:"title" form:"title"` // 标题
Content string `gorm:"type:longtext" json:"content" form:"content"` // 内容
- ImageList string `gorm:"type:longtext" json:"imageList" form:"imageList"` // 图片
Recommend bool `gorm:"not null;index:idx_recommend" json:"recommend" form:"recommend"` // 是否推荐
ViewCount int64 `gorm:"not null" json:"viewCount" form:"viewCount"` // 查看数量
CommentCount int64 `gorm:"not null" json:"commentCount" form:"commentCount"` // 跟帖数量
@@ -179,12 +175,13 @@ type TopicTag struct {
CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间
}
-// 话题点赞
-type TopicLike struct {
+// 用户点赞
+type UserLike struct {
Model
- UserId int64 `gorm:"not null;index:idx_topic_like_user_id;" json:"userId" form:"userId"` // 用户
- TopicId int64 `gorm:"not null;index:idx_topic_like_topic_id;" json:"topicId" form:"topicId"` // 主题编号
- CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间
+ UserId int64 `gorm:"not null;unique_index:idx_user_like_unique;" json:"userId" form:"userId"` // 用户
+ EntityType string `gorm:"not null;size:32;unique_index:idx_user_like_unique;index:idx_user_like_entity;" json:"entityType" form:"entityType"` // 实体类型
+ EntityId int64 `gorm:"not null;unique_index:idx_user_like_unique;index:idx_user_like_entity;" json:"topicId" form:"topicId"` // 实体编号
+ CreateTime int64 `json:"createTime" form:"createTime"` // 创建时间
}
// 动态
diff --git a/server/model/response.go b/server/model/response.go
index 4c4f06cfae033ba8561debd6dd4e006fdc0e4d9c..a2c2d5f8098ff2d49de257315aebfc0d24488d37 100644
--- a/server/model/response.go
+++ b/server/model/response.go
@@ -1,147 +1,156 @@
-package model
-
-import (
- "html/template"
-)
-
-type UserInfo struct {
- Id int64 `json:"id"`
- Username string `json:"username"`
- Email string `json:"email"`
- Nickname string `json:"nickname"`
- Avatar string `json:"avatar"`
- Type int `json:"type"`
- Roles []string `json:"roles"`
- HomePage string `json:"homePage"`
- Description string `json:"description"`
- Score int `json:"score"` // 积分
- TopicCount int `json:"topicCount"` // 话题数量
- CommentCount int `json:"commentCount"` // 跟帖数量
- PasswordSet bool `json:"passwordSet"` // 密码已设置
- Status int `json:"status"`
- CreateTime int64 `json:"createTime"`
-}
-
-func (info *UserInfo) HasRole(role string) bool {
- if len(info.Roles) == 0 {
- return false
- }
- for _, r := range info.Roles {
- if r == role {
- return true
- }
- }
- return false
-}
-
-type TagResponse struct {
- TagId int64 `json:"tagId"`
- TagName string `json:"tagName"`
-}
-
-type ArticleSimpleResponse struct {
- ArticleId int64 `json:"articleId"`
- User *UserInfo `json:"user"`
- Tags *[]TagResponse `json:"tags"`
- Title string `json:"title"`
- Summary string `json:"summary"`
- Share bool `json:"share"`
- SourceUrl string `json:"sourceUrl"`
- ViewCount int64 `json:"viewCount"`
- CreateTime int64 `json:"createTime"`
-}
-
-type ArticleResponse struct {
- ArticleSimpleResponse
- Content template.HTML `json:"content"`
- Toc template.HTML `json:"toc"`
-}
-
-type NodeResponse struct {
- NodeId int64 `json:"nodeId"`
- Name string `json:"name"`
- Description string `json:"description"`
-}
-
-// 帖子列表返回实体
-type TopicSimpleResponse struct {
- TopicId int64 `json:"topicId"`
- Type int `json:"type"`
- User *UserInfo `json:"user"`
- Node *NodeResponse `json:"node"`
- Tags *[]TagResponse `json:"tags"`
- Title string `json:"title"`
- ImageList *[]string `json:"imageList"`
- LastCommentTime int64 `json:"lastCommentTime"`
- ViewCount int64 `json:"viewCount"`
- CommentCount int64 `json:"commentCount"`
- LikeCount int64 `json:"likeCount"`
- Liked bool `json:"liked"`
- CreateTime int64 `json:"createTime"`
-}
-
-// 帖子详情返回实体
-type TopicResponse struct {
- TopicSimpleResponse
- Content template.HTML `json:"content"`
- Toc template.HTML `json:"toc"`
-}
-
-// 项目简单返回
-type ProjectSimpleResponse struct {
- ProjectId int64 `json:"projectId"`
- User *UserInfo `json:"user"`
- Name string `json:"name"`
- Title string `json:"title"`
- Logo string `json:"logo"`
- Url string `json:"url"`
- DocUrl string `json:"docUrl"`
- DownloadUrl string `json:"downloadUrl"`
- Summary string `json:"summary"`
- CreateTime int64 `json:"createTime"`
-}
-
-// 项目详情
-type ProjectResponse struct {
- ProjectSimpleResponse
- Content template.HTML `json:"content"`
-}
-
-type CommentResponse struct {
- CommentId int64 `json:"commentId"`
- User *UserInfo `json:"user"`
- EntityType string `json:"entityType"`
- EntityId int64 `json:"entityId"`
- Content template.HTML `json:"content"`
- QuoteId int64 `json:"quoteId"`
- Quote *CommentResponse `json:"quote"`
- QuoteContent template.HTML `json:"quoteContent"`
- Status int `json:"status"`
- CreateTime int64 `json:"createTime"`
-}
-
-type FavoriteResponse struct {
- FavoriteId int64 `json:"favoriteId"`
- EntityType string `json:"entityType"`
- EntityId int64 `json:"entityId"`
- Deleted bool `json:"deleted"`
- Title string `json:"title"`
- Content string `json:"content"`
- User *UserInfo `json:"user"`
- Url string `json:"url"`
- CreateTime int64 `json:"createTime"`
-}
-
-// 消息
-type MessageResponse struct {
- MessageId int64 `json:"messageId"`
- From *UserInfo `json:"from"` // 消息发送人
- UserId int64 `json:"userId"` // 消息接收人编号
- Content string `json:"content"` // 消息内容
- QuoteContent string `json:"quoteContent"`
- Type int `json:"type"`
- DetailUrl string `json:"detailUrl"` // 消息详情url
- ExtraData string `json:"extraData"`
- Status int `json:"status"`
- CreateTime int64 `json:"createTime"`
-}
+package model
+
+import (
+ "html/template"
+)
+
+type UserInfo struct {
+ Id int64 `json:"id"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ Nickname string `json:"nickname"`
+ Avatar string `json:"avatar"`
+ Type int `json:"type"`
+ Roles []string `json:"roles"`
+ HomePage string `json:"homePage"`
+ Description string `json:"description"`
+ Score int `json:"score"` // 积分
+ TopicCount int `json:"topicCount"` // 话题数量
+ CommentCount int `json:"commentCount"` // 跟帖数量
+ PasswordSet bool `json:"passwordSet"` // 密码已设置
+ Status int `json:"status"`
+ CreateTime int64 `json:"createTime"`
+}
+
+func (info *UserInfo) HasRole(role string) bool {
+ if len(info.Roles) == 0 {
+ return false
+ }
+ for _, r := range info.Roles {
+ if r == role {
+ return true
+ }
+ }
+ return false
+}
+
+type TagResponse struct {
+ TagId int64 `json:"tagId"`
+ TagName string `json:"tagName"`
+}
+
+type ArticleSimpleResponse struct {
+ ArticleId int64 `json:"articleId"`
+ User *UserInfo `json:"user"`
+ Tags *[]TagResponse `json:"tags"`
+ Title string `json:"title"`
+ Summary string `json:"summary"`
+ Share bool `json:"share"`
+ SourceUrl string `json:"sourceUrl"`
+ ViewCount int64 `json:"viewCount"`
+ CreateTime int64 `json:"createTime"`
+}
+
+type ArticleResponse struct {
+ ArticleSimpleResponse
+ Content template.HTML `json:"content"`
+ Toc template.HTML `json:"toc"`
+}
+
+type NodeResponse struct {
+ NodeId int64 `json:"nodeId"`
+ Name string `json:"name"`
+ Description string `json:"description"`
+}
+
+// 帖子列表返回实体
+type TopicSimpleResponse struct {
+ TopicId int64 `json:"topicId"`
+ User *UserInfo `json:"user"`
+ Node *NodeResponse `json:"node"`
+ Tags *[]TagResponse `json:"tags"`
+ Title string `json:"title"`
+ LastCommentTime int64 `json:"lastCommentTime"`
+ ViewCount int64 `json:"viewCount"`
+ CommentCount int64 `json:"commentCount"`
+ LikeCount int64 `json:"likeCount"`
+ Liked bool `json:"liked"`
+ CreateTime int64 `json:"createTime"`
+}
+
+// 帖子详情返回实体
+type TopicResponse struct {
+ TopicSimpleResponse
+ Content template.HTML `json:"content"`
+ Toc template.HTML `json:"toc"`
+}
+
+// 帖子列表返回实体
+type TweetResponse struct {
+ TweetId int64 `json:"tweetId"`
+ User *UserInfo `json:"user"`
+ Content string `json:"content"`
+ ImageList *[]string `json:"imageList"`
+ CommentCount int64 `json:"commentCount"`
+ LikeCount int64 `json:"likeCount"`
+ CreateTime int64 `json:"createTime"`
+}
+
+// 项目简单返回
+type ProjectSimpleResponse struct {
+ ProjectId int64 `json:"projectId"`
+ User *UserInfo `json:"user"`
+ Name string `json:"name"`
+ Title string `json:"title"`
+ Logo string `json:"logo"`
+ Url string `json:"url"`
+ DocUrl string `json:"docUrl"`
+ DownloadUrl string `json:"downloadUrl"`
+ Summary string `json:"summary"`
+ CreateTime int64 `json:"createTime"`
+}
+
+// 项目详情
+type ProjectResponse struct {
+ ProjectSimpleResponse
+ Content template.HTML `json:"content"`
+}
+
+type CommentResponse struct {
+ CommentId int64 `json:"commentId"`
+ User *UserInfo `json:"user"`
+ EntityType string `json:"entityType"`
+ EntityId int64 `json:"entityId"`
+ Content template.HTML `json:"content"`
+ QuoteId int64 `json:"quoteId"`
+ Quote *CommentResponse `json:"quote"`
+ QuoteContent template.HTML `json:"quoteContent"`
+ Status int `json:"status"`
+ CreateTime int64 `json:"createTime"`
+}
+
+type FavoriteResponse struct {
+ FavoriteId int64 `json:"favoriteId"`
+ EntityType string `json:"entityType"`
+ EntityId int64 `json:"entityId"`
+ Deleted bool `json:"deleted"`
+ Title string `json:"title"`
+ Content string `json:"content"`
+ User *UserInfo `json:"user"`
+ Url string `json:"url"`
+ CreateTime int64 `json:"createTime"`
+}
+
+// 消息
+type MessageResponse struct {
+ MessageId int64 `json:"messageId"`
+ From *UserInfo `json:"from"` // 消息发送人
+ UserId int64 `json:"userId"` // 消息接收人编号
+ Content string `json:"content"` // 消息内容
+ QuoteContent string `json:"quoteContent"`
+ Type int `json:"type"`
+ DetailUrl string `json:"detailUrl"` // 消息详情url
+ ExtraData string `json:"extraData"`
+ Status int `json:"status"`
+ CreateTime int64 `json:"createTime"`
+}
diff --git a/server/repositories/topic_like_repository.go b/server/repositories/topic_like_repository.go
deleted file mode 100644
index 9c8e29e47fd13df52308259e6f3295c61de9c22d..0000000000000000000000000000000000000000
--- a/server/repositories/topic_like_repository.go
+++ /dev/null
@@ -1,86 +0,0 @@
-package repositories
-
-import (
- "github.com/jinzhu/gorm"
- "github.com/mlogclub/simple"
-
- "bbs-go/model"
-)
-
-var TopicLikeRepository = newTopicLikeRepository()
-
-func newTopicLikeRepository() *topicLikeRepository {
- return &topicLikeRepository{}
-}
-
-type topicLikeRepository struct {
-}
-
-func (r *topicLikeRepository) Get(db *gorm.DB, id int64) *model.TopicLike {
- ret := &model.TopicLike{}
- if err := db.First(ret, "id = ?", id).Error; err != nil {
- return nil
- }
- return ret
-}
-
-func (r *topicLikeRepository) Take(db *gorm.DB, where ...interface{}) *model.TopicLike {
- ret := &model.TopicLike{}
- if err := db.Take(ret, where...).Error; err != nil {
- return nil
- }
- return ret
-}
-
-func (r *topicLikeRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.TopicLike) {
- cnd.Find(db, &list)
- return
-}
-
-func (r *topicLikeRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.TopicLike {
- ret := &model.TopicLike{}
- if err := cnd.FindOne(db, &ret); err != nil {
- return nil
- }
- return ret
-}
-
-func (r *topicLikeRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.TopicLike, paging *simple.Paging) {
- return r.FindPageByCnd(db, ¶ms.SqlCnd)
-}
-
-func (r *topicLikeRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.TopicLike, paging *simple.Paging) {
- cnd.Find(db, &list)
- count := cnd.Count(db, &model.TopicLike{})
-
- paging = &simple.Paging{
- Page: cnd.Paging.Page,
- Limit: cnd.Paging.Limit,
- Total: count,
- }
- return
-}
-
-func (r *topicLikeRepository) Create(db *gorm.DB, t *model.TopicLike) (err error) {
- err = db.Create(t).Error
- return
-}
-
-func (r *topicLikeRepository) Update(db *gorm.DB, t *model.TopicLike) (err error) {
- err = db.Save(t).Error
- return
-}
-
-func (r *topicLikeRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
- err = db.Model(&model.TopicLike{}).Where("id = ?", id).Updates(columns).Error
- return
-}
-
-func (r *topicLikeRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
- err = db.Model(&model.TopicLike{}).Where("id = ?", id).UpdateColumn(name, value).Error
- return
-}
-
-func (r *topicLikeRepository) Delete(db *gorm.DB, id int64) {
- db.Delete(&model.TopicLike{}, "id = ?", id)
-}
diff --git a/server/repositories/tweet_repository.go b/server/repositories/tweet_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..a581eff942aa19b71a828a1acdf64a102b07c732
--- /dev/null
+++ b/server/repositories/tweet_repository.go
@@ -0,0 +1,90 @@
+package repositories
+
+import (
+ "bbs-go/model"
+ "github.com/mlogclub/simple"
+ "github.com/jinzhu/gorm"
+)
+
+var TweetRepository = newTweetRepository()
+
+func newTweetRepository() *tweetRepository {
+ return &tweetRepository{}
+}
+
+type tweetRepository struct {
+}
+
+func (r *tweetRepository) Get(db *gorm.DB, id int64) *model.Tweet {
+ ret := &model.Tweet{}
+ if err := db.First(ret, "id = ?", id).Error; err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *tweetRepository) Take(db *gorm.DB, where ...interface{}) *model.Tweet {
+ ret := &model.Tweet{}
+ if err := db.Take(ret, where...).Error; err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *tweetRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.Tweet) {
+ cnd.Find(db, &list)
+ return
+}
+
+func (r *tweetRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.Tweet {
+ ret := &model.Tweet{}
+ if err := cnd.FindOne(db, &ret); err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *tweetRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.Tweet, paging *simple.Paging) {
+ return r.FindPageByCnd(db, ¶ms.SqlCnd)
+}
+
+func (r *tweetRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.Tweet, paging *simple.Paging) {
+ cnd.Find(db, &list)
+ count := cnd.Count(db, &model.Tweet{})
+
+ paging = &simple.Paging{
+ Page: cnd.Paging.Page,
+ Limit: cnd.Paging.Limit,
+ Total: count,
+ }
+ return
+}
+
+func (r *tweetRepository) Count(db *gorm.DB, cnd *simple.SqlCnd) int {
+ return cnd.Count(db, &model.Tweet{})
+}
+
+func (r *tweetRepository) Create(db *gorm.DB, t *model.Tweet) (err error) {
+ err = db.Create(t).Error
+ return
+}
+
+func (r *tweetRepository) Update(db *gorm.DB, t *model.Tweet) (err error) {
+ err = db.Save(t).Error
+ return
+}
+
+func (r *tweetRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
+ err = db.Model(&model.Tweet{}).Where("id = ?", id).Updates(columns).Error
+ return
+}
+
+func (r *tweetRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
+ err = db.Model(&model.Tweet{}).Where("id = ?", id).UpdateColumn(name, value).Error
+ return
+}
+
+func (r *tweetRepository) Delete(db *gorm.DB, id int64) {
+ db.Delete(&model.Tweet{}, "id = ?", id)
+}
+
diff --git a/server/repositories/user_like_repository.go b/server/repositories/user_like_repository.go
new file mode 100644
index 0000000000000000000000000000000000000000..1dffcb0a1381fed1a7ae357856a218eadd91174c
--- /dev/null
+++ b/server/repositories/user_like_repository.go
@@ -0,0 +1,86 @@
+package repositories
+
+import (
+ "github.com/jinzhu/gorm"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/model"
+)
+
+var UserLikeRepository = newUserLikeRepository()
+
+func newUserLikeRepository() *userLikeRepository {
+ return &userLikeRepository{}
+}
+
+type userLikeRepository struct {
+}
+
+func (r *userLikeRepository) Get(db *gorm.DB, id int64) *model.UserLike {
+ ret := &model.UserLike{}
+ if err := db.First(ret, "id = ?", id).Error; err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *userLikeRepository) Take(db *gorm.DB, where ...interface{}) *model.UserLike {
+ ret := &model.UserLike{}
+ if err := db.Take(ret, where...).Error; err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *userLikeRepository) Find(db *gorm.DB, cnd *simple.SqlCnd) (list []model.UserLike) {
+ cnd.Find(db, &list)
+ return
+}
+
+func (r *userLikeRepository) FindOne(db *gorm.DB, cnd *simple.SqlCnd) *model.UserLike {
+ ret := &model.UserLike{}
+ if err := cnd.FindOne(db, &ret); err != nil {
+ return nil
+ }
+ return ret
+}
+
+func (r *userLikeRepository) FindPageByParams(db *gorm.DB, params *simple.QueryParams) (list []model.UserLike, paging *simple.Paging) {
+ return r.FindPageByCnd(db, ¶ms.SqlCnd)
+}
+
+func (r *userLikeRepository) FindPageByCnd(db *gorm.DB, cnd *simple.SqlCnd) (list []model.UserLike, paging *simple.Paging) {
+ cnd.Find(db, &list)
+ count := cnd.Count(db, &model.UserLike{})
+
+ paging = &simple.Paging{
+ Page: cnd.Paging.Page,
+ Limit: cnd.Paging.Limit,
+ Total: count,
+ }
+ return
+}
+
+func (r *userLikeRepository) Create(db *gorm.DB, t *model.UserLike) (err error) {
+ err = db.Create(t).Error
+ return
+}
+
+func (r *userLikeRepository) Update(db *gorm.DB, t *model.UserLike) (err error) {
+ err = db.Save(t).Error
+ return
+}
+
+func (r *userLikeRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
+ err = db.Model(&model.UserLike{}).Where("id = ?", id).Updates(columns).Error
+ return
+}
+
+func (r *userLikeRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
+ err = db.Model(&model.UserLike{}).Where("id = ?", id).UpdateColumn(name, value).Error
+ return
+}
+
+func (r *userLikeRepository) Delete(db *gorm.DB, id int64) {
+ db.Delete(&model.UserLike{}, "id = ?", id)
+}
diff --git a/server/services/sys_config_service.go b/server/services/sys_config_service.go
index f6c63f829acde8ba4456617da4de9564b85c009c..290d6aa5a52c23d53c8e934e497c7bc712d793aa 100644
--- a/server/services/sys_config_service.go
+++ b/server/services/sys_config_service.go
@@ -1,163 +1,171 @@
-package services
-
-import (
- "errors"
- "strconv"
- "strings"
-
- "github.com/jinzhu/gorm"
- "github.com/mlogclub/simple"
- "github.com/sirupsen/logrus"
- "github.com/tidwall/gjson"
-
- "bbs-go/model"
- "bbs-go/repositories"
- "bbs-go/services/cache"
-)
-
-var SysConfigService = newSysConfigService()
-
-func newSysConfigService() *sysConfigService {
- return &sysConfigService{}
-}
-
-type sysConfigService struct {
-}
-
-func (s *sysConfigService) Get(id int64) *model.SysConfig {
- return repositories.SysConfigRepository.Get(simple.DB(), id)
-}
-
-func (s *sysConfigService) Take(where ...interface{}) *model.SysConfig {
- return repositories.SysConfigRepository.Take(simple.DB(), where...)
-}
-
-func (s *sysConfigService) Find(cnd *simple.SqlCnd) []model.SysConfig {
- return repositories.SysConfigRepository.Find(simple.DB(), cnd)
-}
-
-func (s *sysConfigService) FindOne(cnd *simple.SqlCnd) *model.SysConfig {
- return repositories.SysConfigRepository.FindOne(simple.DB(), cnd)
-}
-
-func (s *sysConfigService) FindPageByParams(params *simple.QueryParams) (list []model.SysConfig, paging *simple.Paging) {
- return repositories.SysConfigRepository.FindPageByParams(simple.DB(), params)
-}
-
-func (s *sysConfigService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.SysConfig, paging *simple.Paging) {
- return repositories.SysConfigRepository.FindPageByCnd(simple.DB(), cnd)
-}
-
-func (s *sysConfigService) GetAll() []model.SysConfig {
- return repositories.SysConfigRepository.Find(simple.DB(), simple.NewSqlCnd().Asc("id"))
-}
-
-func (s *sysConfigService) SetAll(configStr string) error {
- json := gjson.Parse(configStr)
- configs, ok := json.Value().(map[string]interface{})
- if !ok {
- return errors.New("配置数据格式错误")
- }
- return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- for k, _ := range configs {
- v := json.Get(k).String()
- if err := s.setSingle(tx, k, v, "", ""); err != nil {
- return err
- }
- }
- return nil
- })
-}
-
-// 设置配置,如果配置不存在,那么创建
-func (s *sysConfigService) Set(key, value, name, description string) error {
- return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- if err := s.setSingle(tx, key, value, name, description); err != nil {
- return err
- }
- return nil
- })
-}
-
-func (s *sysConfigService) setSingle(db *gorm.DB, key, value, name, description string) error {
- if len(key) == 0 {
- return errors.New("sys config key is null")
- }
- sysConfig := repositories.SysConfigRepository.GetByKey(db, key)
- if sysConfig == nil {
- sysConfig = &model.SysConfig{
- CreateTime: simple.NowTimestamp(),
- }
- }
- sysConfig.Key = key
- sysConfig.Value = value
- sysConfig.UpdateTime = simple.NowTimestamp()
-
- if len(name) > 0 {
- sysConfig.Name = name
- }
- if len(description) > 0 {
- sysConfig.Description = description
- }
-
- var err error
- if sysConfig.Id > 0 {
- err = repositories.SysConfigRepository.Update(db, sysConfig)
- } else {
- err = repositories.SysConfigRepository.Create(db, sysConfig)
- }
- if err != nil {
- return err
- } else {
- cache.SysConfigCache.Invalidate(key)
- return nil
- }
-}
-
-func (s *sysConfigService) GetConfig() *model.ConfigData {
- var (
- siteTitle = cache.SysConfigCache.GetValue(model.SysConfigSiteTitle)
- siteDescription = cache.SysConfigCache.GetValue(model.SysConfigSiteDescription)
- siteKeywords = cache.SysConfigCache.GetValue(model.SysConfigSiteKeywords)
- siteNavs = cache.SysConfigCache.GetValue(model.SysConfigSiteNavs)
- siteNotification = cache.SysConfigCache.GetValue(model.SysConfigSiteNotification)
- recommendTags = cache.SysConfigCache.GetValue(model.SysConfigRecommendTags)
- urlRedirect = cache.SysConfigCache.GetValue(model.SysConfigUrlRedirect)
- scoreConfigStr = cache.SysConfigCache.GetValue(model.SysConfigScoreConfig)
- defaultNodeIdStr = cache.SysConfigCache.GetValue(model.SysConfigDefaultNodeId)
- )
-
- var siteKeywordsArr []string
- if err := simple.ParseJson(siteKeywords, &siteKeywordsArr); err != nil {
- logrus.Warn("站点关键词数据错误", err)
- }
-
- var siteNavsArr []model.SiteNav
- if err := simple.ParseJson(siteNavs, &siteNavsArr); err != nil {
- logrus.Warn("站点导航数据错误", err)
- }
-
- var recommendTagsArr []string
- if err := simple.ParseJson(recommendTags, &recommendTagsArr); err != nil {
- logrus.Warn("推荐标签数据错误", err)
- }
-
- var scoreConfig model.ScoreConfig
- if err := simple.ParseJson(scoreConfigStr, &scoreConfig); err != nil {
- logrus.Warn("积分配置错误", err)
- }
-
- var defaultNodeId, _ = strconv.ParseInt(defaultNodeIdStr, 10, 64)
-
- return &model.ConfigData{
- SiteTitle: siteTitle,
- SiteDescription: siteDescription,
- SiteKeywords: siteKeywordsArr,
- SiteNavs: siteNavsArr,
- SiteNotification: siteNotification,
- RecommendTags: recommendTagsArr,
- UrlRedirect: strings.ToLower(urlRedirect) == "true",
- ScoreConfig: scoreConfig,
- DefaultNodeId: defaultNodeId,
- }
-}
+package services
+
+import (
+ "errors"
+ "strconv"
+ "strings"
+
+ "github.com/jinzhu/gorm"
+ "github.com/mlogclub/simple"
+ "github.com/sirupsen/logrus"
+ "github.com/tidwall/gjson"
+
+ "bbs-go/model"
+ "bbs-go/repositories"
+ "bbs-go/services/cache"
+)
+
+var SysConfigService = newSysConfigService()
+
+func newSysConfigService() *sysConfigService {
+ return &sysConfigService{}
+}
+
+type sysConfigService struct {
+}
+
+func (s *sysConfigService) Get(id int64) *model.SysConfig {
+ return repositories.SysConfigRepository.Get(simple.DB(), id)
+}
+
+func (s *sysConfigService) Take(where ...interface{}) *model.SysConfig {
+ return repositories.SysConfigRepository.Take(simple.DB(), where...)
+}
+
+func (s *sysConfigService) Find(cnd *simple.SqlCnd) []model.SysConfig {
+ return repositories.SysConfigRepository.Find(simple.DB(), cnd)
+}
+
+func (s *sysConfigService) FindOne(cnd *simple.SqlCnd) *model.SysConfig {
+ return repositories.SysConfigRepository.FindOne(simple.DB(), cnd)
+}
+
+func (s *sysConfigService) FindPageByParams(params *simple.QueryParams) (list []model.SysConfig, paging *simple.Paging) {
+ return repositories.SysConfigRepository.FindPageByParams(simple.DB(), params)
+}
+
+func (s *sysConfigService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.SysConfig, paging *simple.Paging) {
+ return repositories.SysConfigRepository.FindPageByCnd(simple.DB(), cnd)
+}
+
+func (s *sysConfigService) GetAll() []model.SysConfig {
+ return repositories.SysConfigRepository.Find(simple.DB(), simple.NewSqlCnd().Asc("id"))
+}
+
+func (s *sysConfigService) SetAll(configStr string) error {
+ json := gjson.Parse(configStr)
+ configs, ok := json.Value().(map[string]interface{})
+ if !ok {
+ return errors.New("配置数据格式错误")
+ }
+ return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ for k, _ := range configs {
+ v := json.Get(k).String()
+ if err := s.setSingle(tx, k, v, "", ""); err != nil {
+ return err
+ }
+ }
+ return nil
+ })
+}
+
+// 设置配置,如果配置不存在,那么创建
+func (s *sysConfigService) Set(key, value, name, description string) error {
+ return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ if err := s.setSingle(tx, key, value, name, description); err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+func (s *sysConfigService) setSingle(db *gorm.DB, key, value, name, description string) error {
+ if len(key) == 0 {
+ return errors.New("sys config key is null")
+ }
+ sysConfig := repositories.SysConfigRepository.GetByKey(db, key)
+ if sysConfig == nil {
+ sysConfig = &model.SysConfig{
+ CreateTime: simple.NowTimestamp(),
+ }
+ }
+ sysConfig.Key = key
+ sysConfig.Value = value
+ sysConfig.UpdateTime = simple.NowTimestamp()
+
+ if len(name) > 0 {
+ sysConfig.Name = name
+ }
+ if len(description) > 0 {
+ sysConfig.Description = description
+ }
+
+ var err error
+ if sysConfig.Id > 0 {
+ err = repositories.SysConfigRepository.Update(db, sysConfig)
+ } else {
+ err = repositories.SysConfigRepository.Create(db, sysConfig)
+ }
+ if err != nil {
+ return err
+ } else {
+ cache.SysConfigCache.Invalidate(key)
+ return nil
+ }
+}
+
+func (s *sysConfigService) GetConfig() *model.ConfigData {
+ var (
+ siteTitle = cache.SysConfigCache.GetValue(model.SysConfigSiteTitle)
+ siteDescription = cache.SysConfigCache.GetValue(model.SysConfigSiteDescription)
+ siteKeywords = cache.SysConfigCache.GetValue(model.SysConfigSiteKeywords)
+ siteNavs = cache.SysConfigCache.GetValue(model.SysConfigSiteNavs)
+ siteNotification = cache.SysConfigCache.GetValue(model.SysConfigSiteNotification)
+ recommendTags = cache.SysConfigCache.GetValue(model.SysConfigRecommendTags)
+ urlRedirect = cache.SysConfigCache.GetValue(model.SysConfigUrlRedirect)
+ scoreConfigStr = cache.SysConfigCache.GetValue(model.SysConfigScoreConfig)
+ defaultNodeIdStr = cache.SysConfigCache.GetValue(model.SysConfigDefaultNodeId)
+ )
+
+ var siteKeywordsArr []string
+ if len(siteKeywords) > 0 {
+ if err := simple.ParseJson(siteKeywords, &siteKeywordsArr); err != nil {
+ logrus.Warn("站点关键词数据错误", err)
+ }
+ }
+
+ var siteNavsArr []model.SiteNav
+ if len(siteNavs)> 0 {
+ if err := simple.ParseJson(siteNavs, &siteNavsArr); err != nil {
+ logrus.Warn("站点导航数据错误", err)
+ }
+ }
+
+ var recommendTagsArr []string
+ if len(recommendTags)> 0 {
+ if err := simple.ParseJson(recommendTags, &recommendTagsArr); err != nil {
+ logrus.Warn("推荐标签数据错误", err)
+ }
+ }
+
+ var scoreConfig model.ScoreConfig
+ if len(scoreConfigStr)>0 {
+ if err := simple.ParseJson(scoreConfigStr, &scoreConfig); err != nil {
+ logrus.Warn("积分配置错误", err)
+ }
+ }
+
+ var defaultNodeId, _ = strconv.ParseInt(defaultNodeIdStr, 10, 64)
+
+ return &model.ConfigData{
+ SiteTitle: siteTitle,
+ SiteDescription: siteDescription,
+ SiteKeywords: siteKeywordsArr,
+ SiteNavs: siteNavsArr,
+ SiteNotification: siteNotification,
+ RecommendTags: recommendTagsArr,
+ UrlRedirect: strings.ToLower(urlRedirect) == "true",
+ ScoreConfig: scoreConfig,
+ DefaultNodeId: defaultNodeId,
+ }
+}
diff --git a/server/services/topic_like_service.go b/server/services/topic_like_service.go
deleted file mode 100644
index 21b8514a9e01274ed1a8fec62615b3f7e88e66f9..0000000000000000000000000000000000000000
--- a/server/services/topic_like_service.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package services
-
-import (
- "errors"
-
- "github.com/jinzhu/gorm"
- "github.com/mlogclub/simple"
-
- "bbs-go/model"
- "bbs-go/repositories"
-)
-
-var TopicLikeService = newTopicLikeService()
-
-func newTopicLikeService() *topicLikeService {
- return &topicLikeService{}
-}
-
-type topicLikeService struct {
-}
-
-func (s *topicLikeService) Get(id int64) *model.TopicLike {
- return repositories.TopicLikeRepository.Get(simple.DB(), id)
-}
-
-func (s *topicLikeService) Take(where ...interface{}) *model.TopicLike {
- return repositories.TopicLikeRepository.Take(simple.DB(), where...)
-}
-
-func (s *topicLikeService) Find(cnd *simple.SqlCnd) []model.TopicLike {
- return repositories.TopicLikeRepository.Find(simple.DB(), cnd)
-}
-
-func (s *topicLikeService) FindOne(cnd *simple.SqlCnd) *model.TopicLike {
- return repositories.TopicLikeRepository.FindOne(simple.DB(), cnd)
-}
-
-func (s *topicLikeService) FindPageByParams(params *simple.QueryParams) (list []model.TopicLike, paging *simple.Paging) {
- return repositories.TopicLikeRepository.FindPageByParams(simple.DB(), params)
-}
-
-func (s *topicLikeService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.TopicLike, paging *simple.Paging) {
- return repositories.TopicLikeRepository.FindPageByCnd(simple.DB(), cnd)
-}
-
-func (s *topicLikeService) Create(t *model.TopicLike) error {
- return repositories.TopicLikeRepository.Create(simple.DB(), t)
-}
-
-func (s *topicLikeService) Update(t *model.TopicLike) error {
- return repositories.TopicLikeRepository.Update(simple.DB(), t)
-}
-
-func (s *topicLikeService) Updates(id int64, columns map[string]interface{}) error {
- return repositories.TopicLikeRepository.Updates(simple.DB(), id, columns)
-}
-
-func (s *topicLikeService) UpdateColumn(id int64, name string, value interface{}) error {
- return repositories.TopicLikeRepository.UpdateColumn(simple.DB(), id, name, value)
-}
-
-func (s *topicLikeService) Delete(id int64) {
- repositories.TopicLikeRepository.Delete(simple.DB(), id)
-}
-
-// 统计数量
-func (s *topicLikeService) Count(topicId int64) int64 {
- var count int64 = 0
- simple.DB().Model(&model.TopicLike{}).Where("topic_id = ?", topicId).Count(&count)
- return count
-}
-
-// 最近点赞
-func (s *topicLikeService) Recent(topicId int64, count int) []model.TopicLike {
- return s.Find(simple.NewSqlCnd().Eq("topic_id", topicId).Desc("id").Limit(count))
-}
-
-func (s *topicLikeService) Like(userId int64, topicId int64) error {
- topic := repositories.TopicRepository.Get(simple.DB(), topicId)
- if topic == nil || topic.Status != model.StatusOk {
- return errors.New("话题不存在")
- }
-
- // 判断是否已经点赞了
- topicLike := repositories.TopicLikeRepository.Take(simple.DB(), "user_id = ? and topic_id = ?", userId, topicId)
- if topicLike != nil {
- return errors.New("已点赞")
- }
-
- return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- // 点赞
- err := repositories.TopicLikeRepository.Create(tx, &model.TopicLike{
- UserId: userId,
- TopicId: topicId,
- CreateTime: simple.NowTimestamp(),
- })
- if err != nil {
- return err
- }
-
- // 更新帖子点赞数
- return simple.DB().Exec("update t_topic set like_count = like_count + 1 where id = ?", topicId).Error
- })
-}
diff --git a/server/services/topic_service.go b/server/services/topic_service.go
index 9465c5b0b6fe2fc7284740eae92b47b1ced396c8..cbba6a746697064a261d638dffebe20a7479b4df 100644
--- a/server/services/topic_service.go
+++ b/server/services/topic_service.go
@@ -1,315 +1,313 @@
-package services
-
-import (
- "math"
- "path"
- "time"
-
- "github.com/gorilla/feeds"
- "github.com/jinzhu/gorm"
- "github.com/mlogclub/simple"
- "github.com/sirupsen/logrus"
-
- "bbs-go/common"
- "bbs-go/common/baiduseo"
- "bbs-go/common/config"
- "bbs-go/common/urls"
- "bbs-go/model"
- "bbs-go/repositories"
- "bbs-go/services/cache"
-)
-
-type ScanTopicCallback func(topics []model.Topic)
-
-var TopicService = newTopicService()
-
-func newTopicService() *topicService {
- return &topicService{}
-}
-
-type topicService struct{}
-
-func (s *topicService) Get(id int64) *model.Topic {
- return repositories.TopicRepository.Get(simple.DB(), id)
-}
-
-func (s *topicService) Take(where ...interface{}) *model.Topic {
- return repositories.TopicRepository.Take(simple.DB(), where...)
-}
-
-func (s *topicService) Find(cnd *simple.SqlCnd) []model.Topic {
- return repositories.TopicRepository.Find(simple.DB(), cnd)
-}
-
-func (s *topicService) FindOne(cnd *simple.SqlCnd) *model.Topic {
- return repositories.TopicRepository.FindOne(simple.DB(), cnd)
-}
-
-func (s *topicService) FindPageByParams(params *simple.QueryParams) (list []model.Topic, paging *simple.Paging) {
- return repositories.TopicRepository.FindPageByParams(simple.DB(), params)
-}
-
-func (s *topicService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Topic, paging *simple.Paging) {
- return repositories.TopicRepository.FindPageByCnd(simple.DB(), cnd)
-}
-
-func (s *topicService) Count(cnd *simple.SqlCnd) int {
- return repositories.TopicRepository.Count(simple.DB(), cnd)
-}
-
-func (s *topicService) Create(t *model.Topic) error {
- return repositories.TopicRepository.Create(simple.DB(), t)
-}
-
-func (s *topicService) Update(t *model.Topic) error {
- return repositories.TopicRepository.Update(simple.DB(), t)
-}
-
-func (s *topicService) Updates(id int64, columns map[string]interface{}) error {
- return repositories.TopicRepository.Updates(simple.DB(), id, columns)
-}
-
-func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error {
- return repositories.TopicRepository.UpdateColumn(simple.DB(), id, name, value)
-}
-
-// 删除
-func (s *topicService) Delete(id int64) error {
- err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted)
- if err == nil {
- // 删掉标签文章
- TopicTagService.DeleteByTopicId(id)
- }
- return err
-}
-
-// 取消删除
-func (s *topicService) Undelete(id int64) error {
- err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusOk)
- if err == nil {
- // 删掉标签文章
- TopicTagService.UndeleteByTopicId(id)
- }
- return err
-}
-
-// 发表
-func (s *topicService) Publish(topicType int, userId, nodeId int64, tags []string, title, content, imageList string) (*model.Topic, *simple.CodeError) {
- if len(title) == 0 {
- return nil, simple.NewErrorMsg("标题不能为空")
- }
-
- if simple.RuneLen(title) > 128 {
- return nil, simple.NewErrorMsg("标题长度不能超过128")
- }
-
- if nodeId <= 0 {
- nodeId = SysConfigService.GetConfig().DefaultNodeId
- if nodeId <= 0 {
- return nil, simple.NewErrorMsg("请配置默认节点")
- }
- }
- node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId)
- if node == nil || node.Status != model.StatusOk {
- return nil, simple.NewErrorMsg("节点不存在")
- }
-
- now := simple.NowTimestamp()
- topic := &model.Topic{
- Type: topicType,
- UserId: userId,
- NodeId: nodeId,
- Title: title,
- Content: content,
- ImageList: imageList,
- Status: model.StatusOk,
- LastCommentTime: now,
- CreateTime: now,
- }
-
- err := simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- tagIds := repositories.TagRepository.GetOrCreates(tx, tags)
- err := repositories.TopicRepository.Create(tx, topic)
- if err != nil {
- return err
- }
-
- repositories.TopicTagRepository.AddTopicTags(tx, topic.Id, tagIds)
- return nil
- })
- if err == nil {
- // 用户话题计数
- UserService.IncrTopicCount(userId)
- // 获得积分
- UserScoreService.IncrementPostTopicScore(topic)
- // 百度链接推送
- baiduseo.PushUrl(urls.TopicUrl(topic.Id))
- }
- return topic, simple.FromError(err)
-}
-
-// 更新
-func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content string) *simple.CodeError {
- if len(title) == 0 {
- return simple.NewErrorMsg("标题不能为空")
- }
-
- if simple.RuneLen(title) > 128 {
- return simple.NewErrorMsg("标题长度不能超过128")
- }
-
- node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId)
- if node == nil || node.Status != model.StatusOk {
- return simple.NewErrorMsg("节点不存在")
- }
-
- err := simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- err := repositories.TopicRepository.Updates(simple.DB(), topicId, map[string]interface{}{
- "node_id": nodeId,
- "title": title,
- "content": content,
- })
- if err != nil {
- return err
- }
-
- tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建帖子对应标签
- repositories.TopicTagRepository.DeleteTopicTags(tx, topicId) // 先删掉所有的标签
- repositories.TopicTagRepository.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签
- return nil
- })
- return simple.FromError(err)
-}
-
-// 推荐
-func (s *topicService) SetRecommend(topicId int64, recommend bool) error {
- return s.UpdateColumn(topicId, "recommend", recommend)
-}
-
-// 话题的标签
-func (s *topicService) GetTopicTags(topicId int64) []model.Tag {
- topicTags := repositories.TopicTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("topic_id = ?", topicId))
-
- var tagIds []int64
- for _, topicTag := range topicTags {
- tagIds = append(tagIds, topicTag.TagId)
- }
- return cache.TagCache.GetList(tagIds)
-}
-
-// 指定标签下话题列表
-func (s *topicService) GetTagTopics(tagId int64, page int) (topics []model.Topic, paging *simple.Paging) {
- topicTags, paging := repositories.TopicTagRepository.FindPageByCnd(simple.DB(), simple.NewSqlCnd().
- Eq("tag_id", tagId).
- Eq("status", model.StatusOk).
- Page(page, 20).Desc("last_comment_time"))
- if len(topicTags) > 0 {
- var topicIds []int64
- for _, topicTag := range topicTags {
- topicIds = append(topicIds, topicTag.TopicId)
- }
-
- topicsMap := s.GetTopicInIds(topicIds)
- if topicsMap != nil {
- for _, topicTag := range topicTags {
- if topic, found := topicsMap[topicTag.TopicId]; found {
- topics = append(topics, topic)
- }
- }
- }
- }
- return
-}
-
-// GetTopicInIds 根据编号批量获取主题
-func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]model.Topic {
- if len(topicIds) == 0 {
- return nil
- }
- var topics []model.Topic
- simple.DB().Where("id in (?)", topicIds).Find(&topics)
-
- topicsMap := make(map[int64]model.Topic, len(topics))
- for _, topic := range topics {
- topicsMap[topic.Id] = topic
- }
- return topicsMap
-}
-
-// 浏览数+1
-func (s *topicService) IncrViewCount(topicId int64) {
- simple.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId)
-}
-
-// 当帖子被评论的时候,更新最后回复时间、回复数量+1
-func (s *topicService) OnComment(topicId, lastCommentTime int64) {
- simple.Tx(simple.DB(), func(tx *gorm.DB) error {
- if err := tx.Exec("update t_topic set last_comment_time = ?, comment_count = comment_count + 1 where id = ?", lastCommentTime, topicId).Error; err != nil {
- return err
- }
- if err := tx.Exec("update t_topic_tag set last_comment_time = ? where topic_id = ?", lastCommentTime, topicId).Error; err != nil {
- return err
- }
- return nil
- })
-}
-
-// rss
-func (s *topicService) GenerateRss() {
- topics := repositories.TopicRepository.Find(simple.DB(),
- simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000))
-
- var items []*feeds.Item
- for _, topic := range topics {
- topicUrl := urls.TopicUrl(topic.Id)
- user := cache.UserCache.Get(topic.UserId)
- if user == nil {
- continue
- }
- item := &feeds.Item{
- Title: topic.Title,
- Link: &feeds.Link{Href: topicUrl},
- Description: common.GetMarkdownSummary(topic.Content),
- Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String},
- Created: simple.TimeFromTimestamp(topic.CreateTime),
- }
- items = append(items, item)
- }
- siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle)
- siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription)
- feed := &feeds.Feed{
- Title: siteTitle,
- Link: &feeds.Link{Href: config.Conf.BaseUrl},
- Description: siteDescription,
- Author: &feeds.Author{Name: siteTitle},
- Created: time.Now(),
- Items: items,
- }
- atom, err := feed.ToAtom()
- if err != nil {
- logrus.Error(err)
- } else {
- _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_atom.xml"), atom, false)
- }
-
- rss, err := feed.ToRss()
- if err != nil {
- logrus.Error(err)
- } else {
- _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_rss.xml"), rss, false)
- }
-}
-
-// 倒序扫描
-func (s *topicService) ScanDesc(dateFrom, dateTo int64, cb ScanTopicCallback) {
- var cursor int64 = math.MaxInt64
- for {
- list := repositories.TopicRepository.Find(simple.DB(), simple.NewSqlCnd().Lt("id", cursor).
- Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000))
- if list == nil || len(list) == 0 {
- break
- }
- cursor = list[len(list)-1].Id
- cb(list)
- }
-}
+package services
+
+import (
+ "math"
+ "path"
+ "time"
+
+ "github.com/gorilla/feeds"
+ "github.com/jinzhu/gorm"
+ "github.com/mlogclub/simple"
+ "github.com/sirupsen/logrus"
+
+ "bbs-go/common"
+ "bbs-go/common/baiduseo"
+ "bbs-go/common/config"
+ "bbs-go/common/urls"
+ "bbs-go/model"
+ "bbs-go/repositories"
+ "bbs-go/services/cache"
+)
+
+type ScanTopicCallback func(topics []model.Topic)
+
+var TopicService = newTopicService()
+
+func newTopicService() *topicService {
+ return &topicService{}
+}
+
+type topicService struct{}
+
+func (s *topicService) Get(id int64) *model.Topic {
+ return repositories.TopicRepository.Get(simple.DB(), id)
+}
+
+func (s *topicService) Take(where ...interface{}) *model.Topic {
+ return repositories.TopicRepository.Take(simple.DB(), where...)
+}
+
+func (s *topicService) Find(cnd *simple.SqlCnd) []model.Topic {
+ return repositories.TopicRepository.Find(simple.DB(), cnd)
+}
+
+func (s *topicService) FindOne(cnd *simple.SqlCnd) *model.Topic {
+ return repositories.TopicRepository.FindOne(simple.DB(), cnd)
+}
+
+func (s *topicService) FindPageByParams(params *simple.QueryParams) (list []model.Topic, paging *simple.Paging) {
+ return repositories.TopicRepository.FindPageByParams(simple.DB(), params)
+}
+
+func (s *topicService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Topic, paging *simple.Paging) {
+ return repositories.TopicRepository.FindPageByCnd(simple.DB(), cnd)
+}
+
+func (s *topicService) Count(cnd *simple.SqlCnd) int {
+ return repositories.TopicRepository.Count(simple.DB(), cnd)
+}
+
+func (s *topicService) Create(t *model.Topic) error {
+ return repositories.TopicRepository.Create(simple.DB(), t)
+}
+
+func (s *topicService) Update(t *model.Topic) error {
+ return repositories.TopicRepository.Update(simple.DB(), t)
+}
+
+func (s *topicService) Updates(id int64, columns map[string]interface{}) error {
+ return repositories.TopicRepository.Updates(simple.DB(), id, columns)
+}
+
+func (s *topicService) UpdateColumn(id int64, name string, value interface{}) error {
+ return repositories.TopicRepository.UpdateColumn(simple.DB(), id, name, value)
+}
+
+// 删除
+func (s *topicService) Delete(id int64) error {
+ err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusDeleted)
+ if err == nil {
+ // 删掉标签文章
+ TopicTagService.DeleteByTopicId(id)
+ }
+ return err
+}
+
+// 取消删除
+func (s *topicService) Undelete(id int64) error {
+ err := repositories.TopicRepository.UpdateColumn(simple.DB(), id, "status", model.StatusOk)
+ if err == nil {
+ // 删掉标签文章
+ TopicTagService.UndeleteByTopicId(id)
+ }
+ return err
+}
+
+// 发表
+func (s *topicService) Publish(userId, nodeId int64, tags []string, title, content string) (*model.Topic, *simple.CodeError) {
+ if len(title) == 0 {
+ return nil, simple.NewErrorMsg("标题不能为空")
+ }
+
+ if simple.RuneLen(title) > 128 {
+ return nil, simple.NewErrorMsg("标题长度不能超过128")
+ }
+
+ if nodeId <= 0 {
+ nodeId = SysConfigService.GetConfig().DefaultNodeId
+ if nodeId <= 0 {
+ return nil, simple.NewErrorMsg("请配置默认节点")
+ }
+ }
+ node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId)
+ if node == nil || node.Status != model.StatusOk {
+ return nil, simple.NewErrorMsg("节点不存在")
+ }
+
+ now := simple.NowTimestamp()
+ topic := &model.Topic{
+ UserId: userId,
+ NodeId: nodeId,
+ Title: title,
+ Content: content,
+ Status: model.StatusOk,
+ LastCommentTime: now,
+ CreateTime: now,
+ }
+
+ err := simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ tagIds := repositories.TagRepository.GetOrCreates(tx, tags)
+ err := repositories.TopicRepository.Create(tx, topic)
+ if err != nil {
+ return err
+ }
+
+ repositories.TopicTagRepository.AddTopicTags(tx, topic.Id, tagIds)
+ return nil
+ })
+ if err == nil {
+ // 用户话题计数
+ UserService.IncrTopicCount(userId)
+ // 获得积分
+ UserScoreService.IncrementPostTopicScore(topic)
+ // 百度链接推送
+ baiduseo.PushUrl(urls.TopicUrl(topic.Id))
+ }
+ return topic, simple.FromError(err)
+}
+
+// 更新
+func (s *topicService) Edit(topicId, nodeId int64, tags []string, title, content string) *simple.CodeError {
+ if len(title) == 0 {
+ return simple.NewErrorMsg("标题不能为空")
+ }
+
+ if simple.RuneLen(title) > 128 {
+ return simple.NewErrorMsg("标题长度不能超过128")
+ }
+
+ node := repositories.TopicNodeRepository.Get(simple.DB(), nodeId)
+ if node == nil || node.Status != model.StatusOk {
+ return simple.NewErrorMsg("节点不存在")
+ }
+
+ err := simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ err := repositories.TopicRepository.Updates(simple.DB(), topicId, map[string]interface{}{
+ "node_id": nodeId,
+ "title": title,
+ "content": content,
+ })
+ if err != nil {
+ return err
+ }
+
+ tagIds := repositories.TagRepository.GetOrCreates(tx, tags) // 创建帖子对应标签
+ repositories.TopicTagRepository.DeleteTopicTags(tx, topicId) // 先删掉所有的标签
+ repositories.TopicTagRepository.AddTopicTags(tx, topicId, tagIds) // 然后重新添加标签
+ return nil
+ })
+ return simple.FromError(err)
+}
+
+// 推荐
+func (s *topicService) SetRecommend(topicId int64, recommend bool) error {
+ return s.UpdateColumn(topicId, "recommend", recommend)
+}
+
+// 话题的标签
+func (s *topicService) GetTopicTags(topicId int64) []model.Tag {
+ topicTags := repositories.TopicTagRepository.Find(simple.DB(), simple.NewSqlCnd().Where("topic_id = ?", topicId))
+
+ var tagIds []int64
+ for _, topicTag := range topicTags {
+ tagIds = append(tagIds, topicTag.TagId)
+ }
+ return cache.TagCache.GetList(tagIds)
+}
+
+// 指定标签下话题列表
+func (s *topicService) GetTagTopics(tagId int64, page int) (topics []model.Topic, paging *simple.Paging) {
+ topicTags, paging := repositories.TopicTagRepository.FindPageByCnd(simple.DB(), simple.NewSqlCnd().
+ Eq("tag_id", tagId).
+ Eq("status", model.StatusOk).
+ Page(page, 20).Desc("last_comment_time"))
+ if len(topicTags) > 0 {
+ var topicIds []int64
+ for _, topicTag := range topicTags {
+ topicIds = append(topicIds, topicTag.TopicId)
+ }
+
+ topicsMap := s.GetTopicInIds(topicIds)
+ if topicsMap != nil {
+ for _, topicTag := range topicTags {
+ if topic, found := topicsMap[topicTag.TopicId]; found {
+ topics = append(topics, topic)
+ }
+ }
+ }
+ }
+ return
+}
+
+// GetTopicInIds 根据编号批量获取主题
+func (s *topicService) GetTopicInIds(topicIds []int64) map[int64]model.Topic {
+ if len(topicIds) == 0 {
+ return nil
+ }
+ var topics []model.Topic
+ simple.DB().Where("id in (?)", topicIds).Find(&topics)
+
+ topicsMap := make(map[int64]model.Topic, len(topics))
+ for _, topic := range topics {
+ topicsMap[topic.Id] = topic
+ }
+ return topicsMap
+}
+
+// 浏览数+1
+func (s *topicService) IncrViewCount(topicId int64) {
+ simple.DB().Exec("update t_topic set view_count = view_count + 1 where id = ?", topicId)
+}
+
+// 当帖子被评论的时候,更新最后回复时间、回复数量+1
+func (s *topicService) OnComment(topicId, lastCommentTime int64) {
+ simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ if err := tx.Exec("update t_topic set last_comment_time = ?, comment_count = comment_count + 1 where id = ?", lastCommentTime, topicId).Error; err != nil {
+ return err
+ }
+ if err := tx.Exec("update t_topic_tag set last_comment_time = ? where topic_id = ?", lastCommentTime, topicId).Error; err != nil {
+ return err
+ }
+ return nil
+ })
+}
+
+// rss
+func (s *topicService) GenerateRss() {
+ topics := repositories.TopicRepository.Find(simple.DB(),
+ simple.NewSqlCnd().Where("status = ?", model.StatusOk).Desc("id").Limit(1000))
+
+ var items []*feeds.Item
+ for _, topic := range topics {
+ topicUrl := urls.TopicUrl(topic.Id)
+ user := cache.UserCache.Get(topic.UserId)
+ if user == nil {
+ continue
+ }
+ item := &feeds.Item{
+ Title: topic.Title,
+ Link: &feeds.Link{Href: topicUrl},
+ Description: common.GetMarkdownSummary(topic.Content),
+ Author: &feeds.Author{Name: user.Avatar, Email: user.Email.String},
+ Created: simple.TimeFromTimestamp(topic.CreateTime),
+ }
+ items = append(items, item)
+ }
+ siteTitle := cache.SysConfigCache.GetValue(model.SysConfigSiteTitle)
+ siteDescription := cache.SysConfigCache.GetValue(model.SysConfigSiteDescription)
+ feed := &feeds.Feed{
+ Title: siteTitle,
+ Link: &feeds.Link{Href: config.Conf.BaseUrl},
+ Description: siteDescription,
+ Author: &feeds.Author{Name: siteTitle},
+ Created: time.Now(),
+ Items: items,
+ }
+ atom, err := feed.ToAtom()
+ if err != nil {
+ logrus.Error(err)
+ } else {
+ _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_atom.xml"), atom, false)
+ }
+
+ rss, err := feed.ToRss()
+ if err != nil {
+ logrus.Error(err)
+ } else {
+ _ = simple.WriteString(path.Join(config.Conf.StaticPath, "topic_rss.xml"), rss, false)
+ }
+}
+
+// 倒序扫描
+func (s *topicService) ScanDesc(dateFrom, dateTo int64, cb ScanTopicCallback) {
+ var cursor int64 = math.MaxInt64
+ for {
+ list := repositories.TopicRepository.Find(simple.DB(), simple.NewSqlCnd().Lt("id", cursor).
+ Gte("create_time", dateFrom).Lt("create_time", dateTo).Desc("id").Limit(1000))
+ if list == nil || len(list) == 0 {
+ break
+ }
+ cursor = list[len(list)-1].Id
+ cb(list)
+ }
+}
diff --git a/server/services/tweet_service.go b/server/services/tweet_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..b38d0416b2772191f7ccd3aba284439902ae9ca4
--- /dev/null
+++ b/server/services/tweet_service.go
@@ -0,0 +1,93 @@
+package services
+
+import (
+ "github.com/mlogclub/simple"
+
+ "bbs-go/model"
+ "bbs-go/repositories"
+)
+
+var TweetService = newTweetService()
+
+func newTweetService() *tweetService {
+ return &tweetService{}
+}
+
+type tweetService struct {
+}
+
+func (s *tweetService) Get(id int64) *model.Tweet {
+ return repositories.TweetRepository.Get(simple.DB(), id)
+}
+
+func (s *tweetService) Take(where ...interface{}) *model.Tweet {
+ return repositories.TweetRepository.Take(simple.DB(), where...)
+}
+
+func (s *tweetService) Find(cnd *simple.SqlCnd) []model.Tweet {
+ return repositories.TweetRepository.Find(simple.DB(), cnd)
+}
+
+func (s *tweetService) FindOne(cnd *simple.SqlCnd) *model.Tweet {
+ return repositories.TweetRepository.FindOne(simple.DB(), cnd)
+}
+
+func (s *tweetService) FindPageByParams(params *simple.QueryParams) (list []model.Tweet, paging *simple.Paging) {
+ return repositories.TweetRepository.FindPageByParams(simple.DB(), params)
+}
+
+func (s *tweetService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.Tweet, paging *simple.Paging) {
+ return repositories.TweetRepository.FindPageByCnd(simple.DB(), cnd)
+}
+
+func (s *tweetService) Count(cnd *simple.SqlCnd) int {
+ return repositories.TweetRepository.Count(simple.DB(), cnd)
+}
+
+func (s *tweetService) GetTweets(cursor int64) (tweets []model.Tweet, nextCursor int64) {
+ cnd := simple.NewSqlCnd().Eq("status", model.StatusOk).Desc("id").Limit(50)
+ if cursor > 0 {
+ cnd.Lt("id", cursor)
+ }
+ tweets = repositories.TweetRepository.Find(simple.DB(), cnd)
+ if len(tweets) > 0 {
+ nextCursor = tweets[len(tweets)-1].Id
+ } else {
+ nextCursor = cursor
+ }
+ return
+}
+
+func (s *tweetService) Publish(userId int64, content, imageList string) (*model.Tweet, error) {
+ tweet := &model.Tweet{
+ UserId: userId,
+ Content: content,
+ ImageList: imageList,
+ Status: model.StatusOk,
+ CreateTime: simple.NowTimestamp(),
+ }
+ if err := repositories.TweetRepository.Create(simple.DB(), tweet); err != nil {
+ return nil, err
+ }
+ return tweet, nil
+}
+
+func (s *tweetService) Create(t *model.Tweet) error {
+ return repositories.TweetRepository.Create(simple.DB(), t)
+}
+
+func (s *tweetService) Update(t *model.Tweet) error {
+ return repositories.TweetRepository.Update(simple.DB(), t)
+}
+
+func (s *tweetService) Updates(id int64, columns map[string]interface{}) error {
+ return repositories.TweetRepository.Updates(simple.DB(), id, columns)
+}
+
+func (s *tweetService) UpdateColumn(id int64, name string, value interface{}) error {
+ return repositories.TweetRepository.UpdateColumn(simple.DB(), id, name, value)
+}
+
+func (s *tweetService) Delete(id int64) {
+ repositories.TweetRepository.Delete(simple.DB(), id)
+}
diff --git a/server/services/user_like_service.go b/server/services/user_like_service.go
new file mode 100644
index 0000000000000000000000000000000000000000..4865706c88ed3de7c824e087af8da511bc4cbe77
--- /dev/null
+++ b/server/services/user_like_service.go
@@ -0,0 +1,127 @@
+package services
+
+import (
+ "errors"
+
+ "github.com/jinzhu/gorm"
+ "github.com/mlogclub/simple"
+
+ "bbs-go/model"
+ "bbs-go/repositories"
+)
+
+var UserLikeService = newUserLikeService()
+
+func newUserLikeService() *userLikeService {
+ return &userLikeService{}
+}
+
+type userLikeService struct {
+}
+
+func (s *userLikeService) Get(id int64) *model.UserLike {
+ return repositories.UserLikeRepository.Get(simple.DB(), id)
+}
+
+func (s *userLikeService) Take(where ...interface{}) *model.UserLike {
+ return repositories.UserLikeRepository.Take(simple.DB(), where...)
+}
+
+func (s *userLikeService) Find(cnd *simple.SqlCnd) []model.UserLike {
+ return repositories.UserLikeRepository.Find(simple.DB(), cnd)
+}
+
+func (s *userLikeService) FindOne(cnd *simple.SqlCnd) *model.UserLike {
+ return repositories.UserLikeRepository.FindOne(simple.DB(), cnd)
+}
+
+func (s *userLikeService) FindPageByParams(params *simple.QueryParams) (list []model.UserLike, paging *simple.Paging) {
+ return repositories.UserLikeRepository.FindPageByParams(simple.DB(), params)
+}
+
+func (s *userLikeService) FindPageByCnd(cnd *simple.SqlCnd) (list []model.UserLike, paging *simple.Paging) {
+ return repositories.UserLikeRepository.FindPageByCnd(simple.DB(), cnd)
+}
+
+func (s *userLikeService) Create(t *model.UserLike) error {
+ return repositories.UserLikeRepository.Create(simple.DB(), t)
+}
+
+func (s *userLikeService) Update(t *model.UserLike) error {
+ return repositories.UserLikeRepository.Update(simple.DB(), t)
+}
+
+func (s *userLikeService) Updates(id int64, columns map[string]interface{}) error {
+ return repositories.UserLikeRepository.Updates(simple.DB(), id, columns)
+}
+
+func (s *userLikeService) UpdateColumn(id int64, name string, value interface{}) error {
+ return repositories.UserLikeRepository.UpdateColumn(simple.DB(), id, name, value)
+}
+
+func (s *userLikeService) Delete(id int64) {
+ repositories.UserLikeRepository.Delete(simple.DB(), id)
+}
+
+// 统计数量
+func (s *userLikeService) Count(entityType string, entityId int64) int64 {
+ var count int64 = 0
+ simple.DB().Model(&model.UserLike{}).Where("entity_type = ?", entityType).Where("entity_id = ?", entityId).Count(&count)
+ return count
+}
+
+// 最近点赞
+func (s *userLikeService) Recent(entityType string, entityId int64, count int) []model.UserLike {
+ return s.Find(simple.NewSqlCnd().Eq("entity_type", entityType).Eq("entity_id", entityId).Desc("id").Limit(count))
+}
+
+// Exists 是否点赞
+func (s *userLikeService) Exists(userId int64, entityType string, entityId int64) bool {
+ return repositories.UserLikeRepository.FindOne(simple.DB(), simple.NewSqlCnd().Eq("user_id", userId).
+ Eq("entity_type", entityType).Eq("entity_id", entityId)) != nil
+}
+
+// 话题点赞
+func (s *userLikeService) TopicLike(userId int64, topicId int64) error {
+ topic := repositories.TopicRepository.Get(simple.DB(), topicId)
+ if topic == nil || topic.Status != model.StatusOk {
+ return errors.New("话题不存在")
+ }
+
+ return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ if err := s.like(tx, userId, model.EntityTypeTopic, topicId); err != nil {
+ return err
+ }
+ // 更新点赞数
+ return tx.Exec("update t_topic set like_count = like_count + 1 where id = ?", topicId).Error
+ })
+}
+
+// 动态点赞
+func (s *userLikeService) TweetLike(userId int64, tweetId int64) error {
+ tweet := repositories.TweetRepository.Get(simple.DB(), tweetId)
+ if tweet == nil || tweet.Status != model.StatusOk {
+ return errors.New("动态不存在")
+ }
+ return simple.Tx(simple.DB(), func(tx *gorm.DB) error {
+ if err := s.like(tx, userId, model.EntityTypeTweet, tweetId); err != nil {
+ return err
+ }
+ // 更新点赞数
+ return tx.Exec("update t_tweet set like_count = like_count + 1 where id = ?", tweetId).Error
+ })
+}
+
+func (s *userLikeService) like(db *gorm.DB, userId int64, entityType string, entityId int64) error {
+ // 判断是否已经点赞了
+ if s.Exists(userId, entityType, entityId) {
+ return errors.New("已点赞")
+ }
+ // 点赞
+ return repositories.UserLikeRepository.Create(db, &model.UserLike{
+ UserId: userId,
+ EntityType: entityType,
+ EntityId: entityId,
+ CreateTime: simple.NowTimestamp(),
+ })
+}
diff --git a/site/assets/styles/common.scss b/site/assets/styles/common.scss
index c3ae666f7fcb0d1c29fe7814fb6a3e856000a344..367678c59cbb46bb4199b2a97783029404551d48 100644
--- a/site/assets/styles/common.scss
+++ b/site/assets/styles/common.scss
@@ -15,6 +15,12 @@ body {
padding: 1rem 1rem;
}
+.ellipsis {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
@media screen and (max-width: 768px) {
.main {
padding: 1rem 0.5rem 0 !important;
diff --git a/site/assets/styles/main.scss b/site/assets/styles/main.scss
index f12d927108db2e1ef77e389c61507c1efa9d1ec7..aa52dc9298e93d3099d7b0534659bc9613555dde 100644
--- a/site/assets/styles/main.scss
+++ b/site/assets/styles/main.scss
@@ -1,5 +1,6 @@
-@import "./common";
-@import "./article";
-@import "./topic";
-@import "./link";
-@import "./ad";
+@import "./common";
+@import "./article";
+@import "./topic";
+@import "./tweets";
+@import "./link";
+@import "./ad";
diff --git a/site/assets/styles/topic.scss b/site/assets/styles/topic.scss
index 039d59c06bd72c4d1d1631b449faf2e7806f608d..42f7a61c0fd2cd3cba008579c138ca8b9794c56d 100644
--- a/site/assets/styles/topic.scss
+++ b/site/assets/styles/topic.scss
@@ -1,302 +1,243 @@
-.topic-wrap {
- .topic-header {
- display: flex;
-
- @media screen and (max-width: 1024px) {
- .topic-header-right {
- display: none;
- }
- }
-
- .topic-header-left {
- width: 50px;
- height: 50px;
- }
-
- .topic-header-center {
- width: 100%;
- margin-left: 10px;
-
- .topic-title,
- .topic-title a {
- color: #555;
- font-size: 14px;
- font-weight: bold;
- overflow: hidden;
- }
-
- .topic-meta {
- position: relative;
- font-size: 12px;
- line-height: 24px;
- color: #70727c;
- margin-top: 5px;
-
- span.meta-item {
- font-size: 12px;
-
- &:not(:last-child) {
- margin-right: 8px;
- }
-
- &.act {
- // float: right;
- margin-right: 5px;
-
- a {
- font-size: 12px;
- // color: #3273dc;
- color: #7a7a7a;
- font-weight: 700;
-
- &:hover {
- text-decoration: underline;
- }
-
- i {
- font-size: 12px;
- color: #000;
- }
- }
- }
-
- .tag {
- height: auto !important;
-
- &:not(:last-child) {
- margin-right: 3px;
- }
- }
-
- .node {
- font-weight: bold;
- color: #3273dcde;
- // color: #e7672e;
- }
- }
-
- a {
- color: #778087;
- }
- }
- }
-
- .topic-header-right {
- min-width: 100px;
- max-width: 100px;
- text-align: right;
- padding-right: 8px;
-
- .like {
- font-size: 12px;
-
- .like-btn {
- transition: all 0.5s;
- cursor: pointer;
-
- background-color: rgba(126, 107, 1, 0.08);
- color: #e7672e;
- // padding: 0;
- // display: inline-block;
- // height: 20px;
- // width: 20px;
- // line-height: 20px;
- // border-radius: 10px;
- // text-align: center;
- padding: 4px;
- border-radius: 50%;
-
- &:hover,
- &.liked {
- color: #fff;
- background-color: #e7672e;
- }
- }
-
- .like-count {
- font-weight: bold;
- color: #e7672e;
-
- &::before {
- content: ' x ';
- }
- }
- }
-
- .count {
- font-size: 12px;
- color: #fff;
-
- background: #aab0c6;
- padding: 2px 10px;
- border-radius: 6px;
- font-weight: 700;
- }
- }
- }
-
- .topic-content {
- font-size: 15px;
- padding-top: 0 !important;
- }
-
- .topic-actions {
- display: flex;
- height: 42px;
- width: max-content;
- padding: 5px 10px 5px 5px;
-
- background: #fff;
- border: 1px solid #dae0e4;
- border-radius: 30px;
- vertical-align: middle;
-
- .avatar {
- margin-left: 5px;
- border: solid 1px #e8ecee;
- }
-
- .split {
- display: inline-block;
- margin: auto 7px auto 10px;
- height: 16px;
- width: 1px;
- opacity: 0.4;
- background: #dae0e4;
- vertical-align: middle;
- }
-
- .action {
- margin-left: 5px;
- height: 30px;
- width: 30px;
- line-height: 30px;
- text-align: center;
- color: #e7672e;
- background-color: rgba(126, 107, 1, 0.08);
- border-radius: 50%;
- transition: all 0.5s;
- cursor: pointer;
-
- i {
- font-size: 16px;
- }
-
- &:hover,
- &.active {
- color: #fff;
- background-color: #e7672e;
- }
- }
- }
-}
-
-.topic-list {
- margin: 0 0 10px 0 !important;
-
- li.topic-item {
- padding: 8px 0 8px 8px;
- position: relative;
- overflow: hidden;
- transition: background 0.5s;
-
- //&:hover {
- // background: #f3f6f9;
- //}
-
- &:not(:last-child) {
- border-bottom: 1px solid #f2f2f2;
- }
- }
-
- .topic-header {
- .topic-header-center {
- // 控制内容只显示一行,多余的折叠,暂时不需要先注释掉
- //.topic-title,
- //.topic-title a {
- // word-break: break-all;
- // -webkit-line-clamp: 1;
- // text-overflow: ellipsis;
- // -webkit-box-orient: vertical;
- // display: -webkit-box;
- //}
- }
- }
-
- .topic-images {
- margin-top: 8px;
- margin-left: 60px;
-
- $topic-image-size: 120px; // 侧边栏之间的缝隙
- li {
- cursor: pointer;
- border: 1px dashed #ddd;
- text-align: center;
-
- display: inline-block;
- vertical-align: middle;
- width: $topic-image-size;
- height: $topic-image-size;
- line-height: $topic-image-size;
- margin: 0 8px 8px 0;
- background-color: #e8e8e8;
- background-size: 32px 32px;
- background-position: 50%;
- background-repeat: no-repeat;
- overflow: hidden;
- position: relative;
-
- .topic-image-item {
- //min-width: $topic-image-width;
- //min-height: $topic-image-width;
- //width: $topic-image-width;
- //height: $topic-image-width;
- //background-repeat: no-repeat;
- //background-size: cover;
- //background-position: center;
-
- //display: inline-block;
- //max-width: 100%;
- //height: auto;
- //vertical-align: middle;
- //transition: all .5s ease-out .1s;
-
- display: block;
- width: $topic-image-size;
- height: $topic-image-size;
- overflow: hidden;
- transform-style: preserve-3d;
-
- & > img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- transition: all .5s ease-out .1s;
-
- &:hover {
- transform: matrix(1.04,0,0,1.04,0,0);
- backface-visibility: hidden;
- }
- }
- }
- }
- }
-}
-
-.topic-detail {
- margin-bottom: 20px;
-
- .content {
- padding-top: 10px;
- font-size: 15px;
- color: #000;
- white-space: normal;
- word-break: break-all;
- word-wrap: break-word;
- }
-
- .topic-header {
- padding: 10px;
- border-bottom: 1px solid #eee;
- }
-}
+.topic-wrap {
+ .topic-header {
+ display: flex;
+
+ @media screen and (max-width: 1024px) {
+ .topic-header-right {
+ display: none;
+ }
+ }
+
+ .topic-header-left {
+ width: 50px;
+ height: 50px;
+ }
+
+ .topic-header-center {
+ width: 100%;
+ margin-left: 10px;
+
+ .topic-title,
+ .topic-title a {
+ color: #555;
+ font-size: 14px;
+ font-weight: bold;
+ overflow: hidden;
+ }
+
+ .topic-meta {
+ position: relative;
+ font-size: 12px;
+ line-height: 24px;
+ color: #70727c;
+ margin-top: 5px;
+
+ span.meta-item {
+ font-size: 12px;
+
+ &:not(:last-child) {
+ margin-right: 8px;
+ }
+
+ &.act {
+ // float: right;
+ margin-right: 5px;
+
+ a {
+ font-size: 12px;
+ // color: #3273dc;
+ color: #7a7a7a;
+ font-weight: 700;
+
+ &:hover {
+ text-decoration: underline;
+ }
+
+ i {
+ font-size: 12px;
+ color: #000;
+ }
+ }
+ }
+
+ .tag {
+ height: auto !important;
+
+ &:not(:last-child) {
+ margin-right: 3px;
+ }
+ }
+
+ .node {
+ font-weight: bold;
+ color: #3273dcde;
+ // color: #e7672e;
+ }
+ }
+
+ a {
+ color: #778087;
+ }
+ }
+ }
+
+ .topic-header-right {
+ min-width: 100px;
+ max-width: 100px;
+ text-align: right;
+ padding-right: 8px;
+
+ .like {
+ font-size: 12px;
+
+ .like-btn {
+ transition: all 0.5s;
+ cursor: pointer;
+
+ background-color: rgba(126, 107, 1, 0.08);
+ color: #e7672e;
+ // padding: 0;
+ // display: inline-block;
+ // height: 20px;
+ // width: 20px;
+ // line-height: 20px;
+ // border-radius: 10px;
+ // text-align: center;
+ padding: 4px;
+ border-radius: 50%;
+
+ &:hover,
+ &.liked {
+ color: #fff;
+ background-color: #e7672e;
+ }
+ }
+
+ .like-count {
+ font-weight: bold;
+ color: #e7672e;
+
+ &::before {
+ content: ' x ';
+ }
+ }
+ }
+
+ .count {
+ font-size: 12px;
+ color: #fff;
+
+ background: #aab0c6;
+ padding: 2px 10px;
+ border-radius: 6px;
+ font-weight: 700;
+ }
+ }
+ }
+
+ .topic-content {
+ font-size: 15px;
+ padding-top: 0 !important;
+ }
+
+ .topic-actions {
+ display: flex;
+ height: 42px;
+ width: max-content;
+ padding: 5px 10px 5px 5px;
+
+ background: #fff;
+ border: 1px solid #dae0e4;
+ border-radius: 30px;
+ vertical-align: middle;
+
+ .avatar {
+ margin-left: 5px;
+ border: solid 1px #e8ecee;
+ }
+
+ .split {
+ display: inline-block;
+ margin: auto 7px auto 10px;
+ height: 16px;
+ width: 1px;
+ opacity: 0.4;
+ background: #dae0e4;
+ vertical-align: middle;
+ }
+
+ .action {
+ margin-left: 5px;
+ height: 30px;
+ width: 30px;
+ line-height: 30px;
+ text-align: center;
+ color: #e7672e;
+ background-color: rgba(126, 107, 1, 0.08);
+ border-radius: 50%;
+ transition: all 0.5s;
+ cursor: pointer;
+
+ i {
+ font-size: 16px;
+ }
+
+ &:hover,
+ &.active {
+ color: #fff;
+ background-color: #e7672e;
+ }
+ }
+ }
+}
+
+.topic-list {
+ margin: 0 0 10px 0 !important;
+
+ li.topic-item {
+ padding: 8px 0 8px 8px;
+ position: relative;
+ overflow: hidden;
+ transition: background 0.5s;
+
+ //&:hover {
+ // background: #f3f6f9;
+ //}
+
+ &:not(:last-child) {
+ border-bottom: 1px solid #f2f2f2;
+ }
+ }
+
+ .topic-header {
+ .topic-header-center {
+ // 控制内容只显示一行,多余的折叠,暂时不需要先注释掉
+ //.topic-title,
+ //.topic-title a {
+ // word-break: break-all;
+ // -webkit-line-clamp: 1;
+ // text-overflow: ellipsis;
+ // -webkit-box-orient: vertical;
+ // display: -webkit-box;
+ //}
+ }
+ }
+}
+
+.topic-detail {
+ margin-bottom: 20px;
+
+ .content {
+ padding-top: 10px;
+ font-size: 15px;
+ color: #000;
+ white-space: normal;
+ word-break: break-all;
+ word-wrap: break-word;
+ }
+
+ .topic-header {
+ padding: 10px;
+ border-bottom: 1px solid #eee;
+ }
+}
diff --git a/site/assets/styles/tweets.scss b/site/assets/styles/tweets.scss
new file mode 100644
index 0000000000000000000000000000000000000000..5e38c1581193c5e4d8ff7b349f4612121f2c2438
--- /dev/null
+++ b/site/assets/styles/tweets.scss
@@ -0,0 +1,160 @@
+.tweets {
+
+}
+
+.tweet {
+ margin-bottom: 8px;
+ background: #fff;
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
+
+ border-radius: 0.2rem;
+
+ .pin-header-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px 2rem 0 20px;
+
+ .account-group {
+ align-items: center;
+ display: flex;
+
+ .pin-header-content {
+ margin-left: 10px;
+
+ .nickname {
+ font-size: 16px;
+ font-weight: 500;
+ color: #2e3135;
+ }
+
+ .meta-box {
+ display: flex;
+ align-items: center;
+ margin: 0.3rem 0 0;
+ font-size: 13px;
+ color: #8a9aa9;
+ cursor: default;
+
+ .position {
+ max-width: 24rem;
+ }
+ .dot {
+ margin: 0 0.5em;
+ }
+ .time {
+ color: #8a9aa9;
+ }
+ }
+ }
+ }
+ }
+
+ .pin-content-row {
+ position: relative;
+ margin: 5px 4rem 5px 75px;
+
+ .content-box {
+ font-size: 15px;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ color: #17181a;
+ }
+ }
+
+ .pin-image-row {
+ margin: 5px 4rem 5px 75px;
+
+ li {
+ cursor: pointer;
+ border: 1px dashed #ddd;
+ text-align: center;
+
+ // 图片尺寸
+ $image-size: 120px;
+
+ display: inline-block;
+ vertical-align: middle;
+ width: $image-size;
+ height: $image-size;
+ line-height: $image-size;
+ margin: 0 8px 8px 0;
+ background-color: #e8e8e8;
+ background-size: 32px 32px;
+ background-position: 50%;
+ background-repeat: no-repeat;
+ overflow: hidden;
+ position: relative;
+
+ .image-item {
+ display: block;
+ width: $image-size;
+ height: $image-size;
+ overflow: hidden;
+ transform-style: preserve-3d;
+
+ & > img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ transition: all 0.5s ease-out 0.1s;
+
+ &:hover {
+ transform: matrix(1.04, 0, 0, 1.04, 0, 0);
+ backface-visibility: hidden;
+ }
+ }
+ }
+ }
+ }
+
+ .pin-action-row {
+ .action-box {
+ display: flex;
+ position: relative;
+ margin-top: 1.333rem;
+ height: 34px;
+ border-top: 1px solid #ebebeb;
+
+ .action {
+ flex: 1 1 33.333%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ height: 100%;
+ cursor: pointer;
+ user-select: none;
+
+ &:not(:last-child):after {
+ content: '';
+ position: absolute;
+ top: 50%;
+ right: 0;
+ margin-top: -1rem;
+ width: 1px;
+ height: 2rem;
+ background-color: #ebebeb;
+ }
+
+ .action-title-box {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #8a93a0;
+
+ .iconfont {
+ color: #000;
+ }
+
+ .action-title {
+ margin-left: 0.3em;
+ font-size: 13px;
+ font-weight: 500;
+ color: #8a93a0;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/site/components/Comment.vue b/site/components/Comment.vue
index 790a83f648781f2b4183b6c6920c6cd7c9e60b7d..fe8b3b97fd0b9d9ddca1fa725622f85de3242d0d 100644
--- a/site/components/Comment.vue
+++ b/site/components/Comment.vue
@@ -1,7 +1,7 @@
@@ -42,8 +42,8 @@
-
+
{{ title }}
diff --git a/site/components/MyNav.vue b/site/components/MyNav.vue
index a25125ea571002a461ea9a469f4d480b96eb2841..08ed2042e758fac6a3585339c6ca520edd29f212 100644
--- a/site/components/MyNav.vue
+++ b/site/components/MyNav.vue
@@ -12,9 +12,9 @@
@@ -85,7 +85,7 @@
后台管理
-
+
退出登录
diff --git a/site/components/PostTweets.vue b/site/components/PostTweets.vue
index 36b2a791092e0988622fd659ef82e8783df05a8b..76a25fb099a61ede5d7b4aaf790ea6b5b7e885ba 100644
--- a/site/components/PostTweets.vue
+++ b/site/components/PostTweets.vue
@@ -2,24 +2,24 @@
@@ -55,7 +55,7 @@
已删除
编号:{{ item.id }}
-
删除
+
删除
@@ -67,9 +67,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
diff --git a/site/pages/admin/comments/index.vue b/site/pages/admin/comments/index.vue
index 41e08b08f908592c28516e9999a1294e99b90287..1836028350970168e49455dcc866512250a240da 100644
--- a/site/pages/admin/comments/index.vue
+++ b/site/pages/admin/comments/index.vue
@@ -30,10 +30,10 @@
>
-
+
@@ -47,9 +47,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
diff --git a/site/pages/admin/links/index.vue b/site/pages/admin/links/index.vue
index c45de275df2e9c01dda2d17fa5fc34f82e6a8a0e..bdc9a417c2e87e88e2208cdf87bb3dd4fd239b83 100644
--- a/site/pages/admin/links/index.vue
+++ b/site/pages/admin/links/index.vue
@@ -11,19 +11,19 @@
- 查询
+ 查询
- 新增
+ 新增
@@ -61,7 +61,7 @@
- 编辑
@@ -74,9 +74,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
@@ -88,7 +88,7 @@
- Detect
+ Detect
@@ -113,8 +113,8 @@
取消
提交
@@ -158,8 +158,8 @@
取消
提交
diff --git a/site/pages/admin/settings/index.vue b/site/pages/admin/settings/index.vue
index 1332a124cabdb98731a68dbc049f191e0f19d360..a36cf0b0479958b0c7c5a8534c44ff94b353513a 100644
--- a/site/pages/admin/settings/index.vue
+++ b/site/pages/admin/settings/index.vue
@@ -111,11 +111,11 @@
@@ -129,10 +129,10 @@
placement="top"
>
@@ -164,7 +164,7 @@
- 保存
diff --git a/site/pages/admin/topics/index.vue b/site/pages/admin/topics/index.vue
index bc9fc816bfa589de2709bab67aa201e87f5a383f..c80731bdd5efe6f4d1dc57803fde9b8f89d833f8 100644
--- a/site/pages/admin/topics/index.vue
+++ b/site/pages/admin/topics/index.vue
@@ -14,9 +14,9 @@
@@ -25,16 +25,16 @@
- 查询
+ 查询
@@ -70,15 +70,15 @@
已删除
编号:{{ item.id }}
- 删除
- 取消删除
+ 取消删除
- 推荐
- 取消推荐
+ 取消推荐
@@ -89,9 +89,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
@@ -131,8 +131,8 @@
取消
提交
@@ -179,8 +179,8 @@
取消
提交
diff --git a/site/pages/admin/topics/nodes.vue b/site/pages/admin/topics/nodes.vue
index 86ea6a269cd0d5ffa7cf8cf6efe7837086aa2bbb..625ef735cd997205b45e8b7b2945eb51e60ab84b 100644
--- a/site/pages/admin/topics/nodes.vue
+++ b/site/pages/admin/topics/nodes.vue
@@ -6,10 +6,10 @@
- 查询
+ 查询
- 新增
+ 新增
@@ -17,10 +17,10 @@
@@ -41,7 +41,7 @@
- 编辑
@@ -54,9 +54,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
@@ -85,8 +85,8 @@
取消
提交
@@ -127,8 +127,8 @@
取消
提交
diff --git a/site/pages/admin/users/index.vue b/site/pages/admin/users/index.vue
index 42759fe3a121e267045cc6c099da9b021a6b55de..70b0256c53b5f9e40ad99b6c20dc32658de8289c 100644
--- a/site/pages/admin/users/index.vue
+++ b/site/pages/admin/users/index.vue
@@ -12,10 +12,10 @@
- 查询
+ 查询
- 新增
+ 新增
@@ -23,10 +23,10 @@
@@ -93,7 +93,7 @@
- 编辑
@@ -106,9 +106,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
@@ -144,8 +144,8 @@
取消
提交
@@ -209,8 +209,8 @@
取消
提交
diff --git a/site/pages/admin/users/score-log.vue b/site/pages/admin/users/score-log.vue
index a7cef38ea522e8219a106b3ec30b22fa132e66d5..c2c87f5a246f93e2cc48e64372e30a0f8fc881e0 100644
--- a/site/pages/admin/users/score-log.vue
+++ b/site/pages/admin/users/score-log.vue
@@ -35,9 +35,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
diff --git a/site/pages/admin/users/score.vue b/site/pages/admin/users/score.vue
index b72c9244d62f95d67b1c711c4a43bc51246a438c..a53cf31ed03910163f1c5e944f00d868af09886d 100644
--- a/site/pages/admin/users/score.vue
+++ b/site/pages/admin/users/score.vue
@@ -6,7 +6,7 @@
- 查询
+ 查询
@@ -38,9 +38,9 @@
积分记录
@@ -53,9 +53,9 @@
:current-page="page.page"
:page-size="page.limit"
:total="page.total"
- layout="total, sizes, prev, pager, next, jumper"
@current-change="handlePageChange"
@size-change="handleLimitChange"
+ layout="total, sizes, prev, pager, next, jumper"
>
diff --git a/site/pages/article/_id.vue b/site/pages/article/_id.vue
index d7b535f2e869b980c9ee208f0de0d393633d7cc2..f6d1afc2d8e73bd0cb62e5e2c343be7e7cc13342 100644
--- a/site/pages/article/_id.vue
+++ b/site/pages/article/_id.vue
@@ -93,9 +93,9 @@
@@ -205,7 +205,7 @@ export default {
} catch (e) {
error({
statusCode: 404,
- message: '文章不存在,或已被删除'
+ message: '文章不存在或被删除'
})
return
}
diff --git a/site/pages/article/create.vue b/site/pages/article/create.vue
index 599f583235e981cf440146a187652221600d2d55..7448c262c33e5fc2fb2610fe4d98c0973d32bc40 100644
--- a/site/pages/article/create.vue
+++ b/site/pages/article/create.vue
@@ -52,8 +52,8 @@
发表
diff --git a/site/pages/article/edit/_id.vue b/site/pages/article/edit/_id.vue
index afdf55b841adc9a9cac096de872ce9bac54ec734..8a5602e5466cfc6c5c5d08fea4259c0bafe67a80 100644
--- a/site/pages/article/edit/_id.vue
+++ b/site/pages/article/edit/_id.vue
@@ -51,8 +51,8 @@
提交更改
diff --git a/site/pages/link/submit.vue b/site/pages/link/submit.vue
index 1dff3e1a2a24dfb3ea8c18c7366c22b5536f97e5..9413fca508cc15632294a23bc4c13c61d491e486 100644
--- a/site/pages/link/submit.vue
+++ b/site/pages/link/submit.vue
@@ -23,11 +23,11 @@
@@ -37,10 +37,10 @@
@@ -50,10 +50,10 @@
@@ -63,10 +63,10 @@
@@ -76,8 +76,8 @@
diff --git a/site/pages/project/_id.vue b/site/pages/project/_id.vue
index 81cfb92af3c6dcb877c1fb7ca1692418117ba971..571948c2e47fd9b957ae5968c5c5524bab0f57f1 100644
--- a/site/pages/project/_id.vue
+++ b/site/pages/project/_id.vue
@@ -27,8 +27,8 @@
-
diff --git a/site/pages/topic/create.vue b/site/pages/topic/create.vue
index bf77acec356a8e470c7d9555ea86fabcacd96bca..8689298c7a260d9c0c7e417cd538a893b1367d6a 100644
--- a/site/pages/topic/create.vue
+++ b/site/pages/topic/create.vue
@@ -67,8 +67,8 @@
发表主题
diff --git a/site/pages/topic/edit/_id.vue b/site/pages/topic/edit/_id.vue
index 6a05c867bfc90b0209c284e34a42d234e5598511..e663711b39f978c7e8ad497745865dff7cd98c68 100644
--- a/site/pages/topic/edit/_id.vue
+++ b/site/pages/topic/edit/_id.vue
@@ -66,8 +66,8 @@
提交更改
diff --git a/site/pages/tweet/_id.vue b/site/pages/tweet/_id.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6db1a2e848cf259491cc4de8babe646de3eb6257
--- /dev/null
+++ b/site/pages/tweet/_id.vue
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
diff --git a/site/pages/tweets/index.vue b/site/pages/tweets/index.vue
index 9236ca9f4ca43d997cab6cc00030b2e5dbf0837c..4d7d63a599dcde4a9551ff25043a065734d363ad 100644
--- a/site/pages/tweets/index.vue
+++ b/site/pages/tweets/index.vue
@@ -3,8 +3,18 @@
@@ -14,24 +24,33 @@