From 69ced627157d08f34526be12254907e087e0caa5 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Tue, 5 Mar 2024 23:40:54 +0800 Subject: [PATCH] :recycle: Use the universal post api replace some duplicated apis --- pkg/models/feed.go | 20 +++ pkg/server/articles_api.go | 150 ++++-------------- pkg/server/comments_api.go | 145 +++-------------- pkg/server/feed_api.go | 28 +--- pkg/server/moments_api.go | 145 +++-------------- pkg/server/posts_api.go | 126 +++++++++++++++ pkg/server/startup.go | 20 ++- pkg/services/posts.go | 140 +++++++--------- .../src/components/posts/MomentContent.vue | 2 +- 9 files changed, 295 insertions(+), 481 deletions(-) create mode 100644 pkg/models/feed.go create mode 100644 pkg/server/posts_api.go diff --git a/pkg/models/feed.go b/pkg/models/feed.go new file mode 100644 index 0000000..e68b7fe --- /dev/null +++ b/pkg/models/feed.go @@ -0,0 +1,20 @@ +package models + +type Feed struct { + BaseModel + + Alias string `json:"alias"` + Title string `json:"title"` + Description string `json:"description"` + Content string `json:"content"` + ModelType string `json:"model_type"` + + CommentCount int64 `json:"comment_count"` + ReactionCount int64 `json:"reaction_count"` + + AuthorID uint `json:"author_id"` + RealmID *uint `json:"realm_id"` + + Author Account `json:"author" gorm:"embedded"` + ReactionList map[string]int64 `json:"reaction_list" gorm:"-"` +} diff --git a/pkg/server/articles_api.go b/pkg/server/articles_api.go index 8e44a89..5728014 100644 --- a/pkg/server/articles_api.go +++ b/pkg/server/articles_api.go @@ -9,81 +9,18 @@ import ( "code.smartsheep.studio/hydrogen/interactive/pkg/services" "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/samber/lo" ) -func contextArticle() *services.PostTypeContext[*models.Article] { - return &services.PostTypeContext[*models.Article]{ - Tx: database.C, - TypeName: "Article", - CanReply: false, - CanRepost: false, +func contextArticle() *services.PostTypeContext { + return &services.PostTypeContext{ + Tx: database.C, + TableName: "articles", + ColumnName: "article", + CanReply: false, + CanRepost: false, } } -func getArticle(c *fiber.Ctx) error { - alias := c.Params("articleId") - - mx := contextArticle().FilterPublishedAt(time.Now()) - - item, err := mx.GetViaAlias(alias) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - item.ReactionList, err = mx.CountReactions(item.ID) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - return c.JSON(item) -} - -func listArticle(c *fiber.Ctx) error { - take := c.QueryInt("take", 0) - offset := c.QueryInt("offset", 0) - realmId := c.QueryInt("realmId", 0) - - mx := contextArticle(). - FilterPublishedAt(time.Now()). - FilterRealm(uint(realmId)). - SortCreatedAt("desc") - - var author models.Account - if len(c.Query("authorId")) > 0 { - if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - mx = mx.FilterAuthor(author.ID) - } - - if len(c.Query("category")) > 0 { - mx = mx.FilterWithCategory(c.Query("category")) - } - if len(c.Query("tag")) > 0 { - mx = mx.FilterWithTag(c.Query("tag")) - } - - if !c.QueryBool("reply", true) { - mx = mx.FilterReply(true) - } - - count, err := mx.Count() - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - items, err := mx.List(take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(fiber.Map{ - "count": count, - "data": items, - }) -} - func createArticle(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) @@ -105,8 +42,6 @@ func createArticle(c *fiber.Ctx) error { data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") } - mx := contextArticle() - item := &models.Article{ PostBase: models.PostBase{ Alias: data.Alias, @@ -131,12 +66,11 @@ func createArticle(c *fiber.Ctx) error { } } - item, err := mx.New(item) - if err != nil { + if item, err := services.NewPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else { + return c.JSON(item) } - - return c.JSON(item) } func editArticle(c *fiber.Ctx) error { @@ -158,10 +92,13 @@ func editArticle(c *fiber.Ctx) error { return err } - mx := contextArticle().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id)) - if err != nil { + var item *models.Article + if err := database.C.Where(models.Article{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } @@ -174,61 +111,28 @@ func editArticle(c *fiber.Ctx) error { item.Categories = data.Categories item.Attachments = data.Attachments - item, err = mx.Edit(item) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(item) -} - -func reactArticle(c *fiber.Ctx) error { - user := c.Locals("principal").(models.Account) - id, _ := c.ParamsInt("articleId", 0) - - var data struct { - Symbol string `json:"symbol" validate:"required"` - Attitude models.ReactionAttitude `json:"attitude" validate:"required"` - } - - if err := BindAndValidate(c, &data); err != nil { - return err - } - - mx := contextArticle() - - item, err := mx.Get(uint(id), true) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - reaction := models.Reaction{ - Symbol: data.Symbol, - Attitude: data.Attitude, - AccountID: user.ID, - ArticleID: &item.ID, - } - - if positive, reaction, err := mx.React(reaction); err != nil { + if item, err := services.EditPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } else { - return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) + return c.JSON(item) } - } func deleteArticle(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) id, _ := c.ParamsInt("articleId", 0) - mx := contextArticle().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id), true) - if err != nil { + var item *models.Article + if err := database.C.Where(models.Article{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } - if err := mx.Delete(item); err != nil { + if err := services.DeletePost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/server/comments_api.go b/pkg/server/comments_api.go index d76dfef..869c35a 100644 --- a/pkg/server/comments_api.go +++ b/pkg/server/comments_api.go @@ -10,81 +10,18 @@ import ( "code.smartsheep.studio/hydrogen/interactive/pkg/services" "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/samber/lo" ) -func contextComment() *services.PostTypeContext[*models.Comment] { - return &services.PostTypeContext[*models.Comment]{ - Tx: database.C, - TypeName: "Comment", - CanReply: false, - CanRepost: true, +func contextComment() *services.PostTypeContext { + return &services.PostTypeContext{ + Tx: database.C, + TableName: "comments", + ColumnName: "comment", + CanReply: false, + CanRepost: true, } } -func getComment(c *fiber.Ctx) error { - alias := c.Params("commentId") - - mx := contextComment().FilterPublishedAt(time.Now()) - - item, err := mx.GetViaAlias(alias) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - item.ReactionList, err = mx.CountReactions(item.ID) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - return c.JSON(item) -} - -func listComment(c *fiber.Ctx) error { - take := c.QueryInt("take", 0) - offset := c.QueryInt("offset", 0) - realmId := c.QueryInt("realmId", 0) - - mx := contextComment(). - FilterPublishedAt(time.Now()). - FilterRealm(uint(realmId)). - SortCreatedAt("desc") - - var author models.Account - if len(c.Query("authorId")) > 0 { - if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - mx = mx.FilterAuthor(author.ID) - } - - if len(c.Query("category")) > 0 { - mx = mx.FilterWithCategory(c.Query("category")) - } - if len(c.Query("tag")) > 0 { - mx = mx.FilterWithTag(c.Query("tag")) - } - - if !c.QueryBool("reply", true) { - mx = mx.FilterReply(true) - } - - count, err := mx.Count() - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - items, err := mx.List(take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(fiber.Map{ - "count": count, - "data": items, - }) -} - func createComment(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) @@ -106,8 +43,6 @@ func createComment(c *fiber.Ctx) error { data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") } - mx := contextComment() - item := &models.Comment{ PostBase: models.PostBase{ Alias: data.Alias, @@ -142,7 +77,7 @@ func createComment(c *fiber.Ctx) error { } } - item, err := mx.New(item) + item, err := services.NewPost(item) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -167,10 +102,13 @@ func editComment(c *fiber.Ctx) error { return err } - mx := contextComment().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id)) - if err != nil { + var item *models.Comment + if err := database.C.Where(models.Comment{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } @@ -181,61 +119,28 @@ func editComment(c *fiber.Ctx) error { item.Categories = data.Categories item.Attachments = data.Attachments - item, err = mx.Edit(item) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(item) -} - -func reactComment(c *fiber.Ctx) error { - user := c.Locals("principal").(models.Account) - id, _ := c.ParamsInt("commentId", 0) - - var data struct { - Symbol string `json:"symbol" validate:"required"` - Attitude models.ReactionAttitude `json:"attitude" validate:"required"` - } - - if err := BindAndValidate(c, &data); err != nil { - return err - } - - mx := contextComment() - - item, err := mx.Get(uint(id), true) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - reaction := models.Reaction{ - Symbol: data.Symbol, - Attitude: data.Attitude, - AccountID: user.ID, - CommentID: &item.ID, - } - - if positive, reaction, err := mx.React(reaction); err != nil { + if item, err := services.EditPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } else { - return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) + return c.JSON(item) } - } func deleteComment(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) id, _ := c.ParamsInt("commentId", 0) - mx := contextComment().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id), true) - if err != nil { + var item *models.Comment + if err := database.C.Where(models.Comment{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } - if err := mx.Delete(item); err != nil { + if err := services.DeletePost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/server/feed_api.go b/pkg/server/feed_api.go index db5307c..beee2cf 100644 --- a/pkg/server/feed_api.go +++ b/pkg/server/feed_api.go @@ -1,6 +1,5 @@ package server -import "C" import ( "code.smartsheep.studio/hydrogen/interactive/pkg/database" "code.smartsheep.studio/hydrogen/interactive/pkg/models" @@ -10,23 +9,6 @@ import ( "github.com/spf13/viper" ) -type FeedItem struct { - models.BaseModel - - Alias string `json:"alias"` - Title string `json:"title"` - Description string `json:"description"` - Content string `json:"content"` - ModelType string `json:"model_type"` - CommentCount int64 `json:"comment_count"` - ReactionCount int64 `json:"reaction_count"` - AuthorID uint `json:"author_id"` - RealmID *uint `json:"realm_id"` - - Author models.Account `json:"author" gorm:"embedded"` - ReactionList map[string]int64 `json:"reaction_list"` -} - const ( queryArticle = "id, created_at, updated_at, alias, title, NULL as content, description, realm_id, author_id, 'article' as model_type" queryMoment = "id, created_at, updated_at, alias, NULL as title, content, NULL as description, realm_id, author_id, 'moment' as model_type" @@ -58,7 +40,7 @@ func listFeed(c *fiber.Ctx) error { } } - var result []*FeedItem + var result []*models.Feed userTable := viper.GetString("database.prefix") + "accounts" commentTable := viper.GetString("database.prefix") + "comments" @@ -94,15 +76,15 @@ func listFeed(c *fiber.Ctx) error { } revertReaction := func(dataset string) error { - itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *FeedItem, index int) (*FeedItem, bool) { + itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *models.Feed, index int) (*models.Feed, bool) { return item, item.ModelType == dataset - }), func(item *FeedItem) (uint, *FeedItem) { + }), func(item *models.Feed) (uint, *models.Feed) { return item.ID, item }) - idx := lo.Map(lo.Filter(result, func(item *FeedItem, index int) bool { + idx := lo.Map(lo.Filter(result, func(item *models.Feed, index int) bool { return item.ModelType == dataset - }), func(item *FeedItem, index int) uint { + }), func(item *models.Feed, index int) uint { return item.ID }) diff --git a/pkg/server/moments_api.go b/pkg/server/moments_api.go index c3ec2b0..2e295c8 100644 --- a/pkg/server/moments_api.go +++ b/pkg/server/moments_api.go @@ -9,81 +9,18 @@ import ( "code.smartsheep.studio/hydrogen/interactive/pkg/services" "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/samber/lo" ) -func contextMoment() *services.PostTypeContext[*models.Moment] { - return &services.PostTypeContext[*models.Moment]{ - Tx: database.C, - TypeName: "Moment", - CanReply: false, - CanRepost: true, +func contextMoment() *services.PostTypeContext { + return &services.PostTypeContext{ + Tx: database.C, + TableName: "moments", + ColumnName: "moment", + CanReply: false, + CanRepost: true, } } -func getMoment(c *fiber.Ctx) error { - alias := c.Params("momentId") - - mx := contextMoment().FilterPublishedAt(time.Now()) - - item, err := mx.GetViaAlias(alias) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - item.ReactionList, err = mx.CountReactions(item.ID) - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - return c.JSON(item) -} - -func listMoment(c *fiber.Ctx) error { - take := c.QueryInt("take", 0) - offset := c.QueryInt("offset", 0) - realmId := c.QueryInt("realmId", 0) - - mx := contextMoment(). - FilterPublishedAt(time.Now()). - FilterRealm(uint(realmId)). - SortCreatedAt("desc") - - var author models.Account - if len(c.Query("authorId")) > 0 { - if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - mx = mx.FilterAuthor(author.ID) - } - - if len(c.Query("category")) > 0 { - mx = mx.FilterWithCategory(c.Query("category")) - } - if len(c.Query("tag")) > 0 { - mx = mx.FilterWithTag(c.Query("tag")) - } - - if !c.QueryBool("reply", true) { - mx = mx.FilterReply(true) - } - - count, err := mx.Count() - if err != nil { - return fiber.NewError(fiber.StatusInternalServerError, err.Error()) - } - - items, err := mx.List(take, offset) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(fiber.Map{ - "count": count, - "data": items, - }) -} - func createMoment(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) @@ -104,8 +41,6 @@ func createMoment(c *fiber.Ctx) error { data.Alias = strings.ReplaceAll(uuid.NewString(), "-", "") } - mx := contextMoment() - item := &models.Moment{ PostBase: models.PostBase{ Alias: data.Alias, @@ -140,7 +75,7 @@ func createMoment(c *fiber.Ctx) error { } } - item, err := mx.New(item) + item, err := services.NewPost(item) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -165,10 +100,13 @@ func editMoment(c *fiber.Ctx) error { return err } - mx := contextMoment().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id)) - if err != nil { + var item *models.Moment + if err := database.C.Where(models.Comment{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } @@ -179,61 +117,28 @@ func editMoment(c *fiber.Ctx) error { item.Categories = data.Categories item.Attachments = data.Attachments - item, err = mx.Edit(item) - if err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - return c.JSON(item) -} - -func reactMoment(c *fiber.Ctx) error { - user := c.Locals("principal").(models.Account) - id, _ := c.ParamsInt("momentId", 0) - - var data struct { - Symbol string `json:"symbol" validate:"required"` - Attitude models.ReactionAttitude `json:"attitude" validate:"required"` - } - - if err := BindAndValidate(c, &data); err != nil { - return err - } - - mx := contextMoment() - - item, err := mx.Get(uint(id), true) - if err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - - reaction := models.Reaction{ - Symbol: data.Symbol, - Attitude: data.Attitude, - AccountID: user.ID, - MomentID: &item.ID, - } - - if positive, reaction, err := mx.React(reaction); err != nil { + if item, err := services.EditPost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } else { - return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) + return c.JSON(item) } - } func deleteMoment(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) id, _ := c.ParamsInt("momentId", 0) - mx := contextMoment().FilterAuthor(user.ID) - - item, err := mx.Get(uint(id), true) - if err != nil { + var item *models.Moment + if err := database.C.Where(models.Comment{ + PostBase: models.PostBase{ + BaseModel: models.BaseModel{ID: uint(id)}, + AuthorID: user.ID, + }, + }).First(&item).Error; err != nil { return fiber.NewError(fiber.StatusNotFound, err.Error()) } - if err := mx.Delete(item); err != nil { + if err := services.DeletePost(item); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/server/posts_api.go b/pkg/server/posts_api.go new file mode 100644 index 0000000..26224ed --- /dev/null +++ b/pkg/server/posts_api.go @@ -0,0 +1,126 @@ +package server + +import ( + "time" + + "code.smartsheep.studio/hydrogen/interactive/pkg/database" + "code.smartsheep.studio/hydrogen/interactive/pkg/models" + "code.smartsheep.studio/hydrogen/interactive/pkg/services" + "github.com/gofiber/fiber/v2" + "github.com/samber/lo" +) + +var postContextKey = "ptx" + +func useDynamicContext(c *fiber.Ctx) error { + postType := c.Params("postType") + switch postType { + case "articles": + c.Locals(postContextKey, contextArticle()) + case "moments": + c.Locals(postContextKey, contextMoment()) + case "comments": + c.Locals(postContextKey, contextComment()) + default: + return fiber.NewError(fiber.StatusBadRequest, "invalid dataset") + } + + return c.Next() +} + +func getPost(c *fiber.Ctx) error { + alias := c.Params("postId") + + mx := c.Locals(postContextKey).(*services.PostTypeContext) + + item, err := mx.GetViaAlias(alias) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + item.ReactionList, err = mx.CountReactions(item.ID) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(item) +} + +func listPost(c *fiber.Ctx) error { + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + realmId := c.QueryInt("realmId", 0) + + mx := c.Locals(postContextKey).(*services.PostTypeContext). + FilterPublishedAt(time.Now()). + FilterRealm(uint(realmId)). + SortCreatedAt("desc") + + var author models.Account + if len(c.Query("authorId")) > 0 { + if err := database.C.Where(&models.Account{Name: c.Query("authorId")}).First(&author).Error; err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + mx = mx.FilterAuthor(author.ID) + } + + if len(c.Query("category")) > 0 { + mx = mx.FilterWithCategory(c.Query("category")) + } + if len(c.Query("tag")) > 0 { + mx = mx.FilterWithTag(c.Query("tag")) + } + + if !c.QueryBool("reply", true) { + mx = mx.FilterReply(true) + } + + count, err := mx.Count() + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + items, err := mx.List(take, offset) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": items, + }) +} + +func reactPost(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + id, _ := c.ParamsInt("articleId", 0) + + var data struct { + Symbol string `json:"symbol" validate:"required"` + Attitude models.ReactionAttitude `json:"attitude" validate:"required"` + } + + if err := BindAndValidate(c, &data); err != nil { + return err + } + + mx := c.Locals(postContextKey).(*services.PostTypeContext) + + item, err := mx.Get(uint(id), true) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + reaction := models.Reaction{ + Symbol: data.Symbol, + Attitude: data.Attitude, + AccountID: user.ID, + ArticleID: &item.ID, + } + + if positive, reaction, err := mx.React(reaction); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } else { + return c.Status(lo.Ternary(positive, fiber.StatusCreated, fiber.StatusNoContent)).JSON(reaction) + } +} diff --git a/pkg/server/startup.go b/pkg/server/startup.go index bbbd2ff..aa8b829 100644 --- a/pkg/server/startup.go +++ b/pkg/server/startup.go @@ -71,32 +71,30 @@ func NewServer() { api.Get("/feed", listFeed) + posts := api.Group("/p/:postType").Use(useDynamicContext).Name("Dataset Universal API") + { + posts.Get("/", listPost) + posts.Get("/:postId", getPost) + posts.Post("/:postId/react", authMiddleware, reactPost) + } + moments := api.Group("/moments").Name("Moments API") { - moments.Get("/", listMoment) - moments.Get("/:momentId", getMoment) moments.Post("/", authMiddleware, createMoment) - moments.Post("/:momentId/react", authMiddleware, reactMoment) moments.Put("/:momentId", authMiddleware, editMoment) moments.Delete("/:momentId", authMiddleware, deleteMoment) } - articles := api.Group("/articles").Name("Articles API") + articles := api.Group("/p/articles").Name("Articles API") { - articles.Get("/", listArticle) - articles.Get("/:articleId", getArticle) articles.Post("/", authMiddleware, createArticle) - articles.Post("/:articleId/react", authMiddleware, reactArticle) articles.Put("/:articleId", authMiddleware, editArticle) articles.Delete("/:articleId", authMiddleware, deleteArticle) } - comments := api.Group("/comments").Name("Comments API") + comments := api.Group("/p/comments").Name("Comments API") { - comments.Get("/", listComment) - comments.Get("/:commentId", getComment) comments.Post("/", authMiddleware, createComment) - comments.Post("/:commentId/react", authMiddleware, reactComment) comments.Put("/:commentId", authMiddleware, editComment) comments.Delete("/:commentId", authMiddleware, deleteComment) } diff --git a/pkg/services/posts.go b/pkg/services/posts.go index 78480cd..3e5d6d2 100644 --- a/pkg/services/posts.go +++ b/pkg/services/posts.go @@ -1,81 +1,50 @@ package services -import "C" import ( + "errors" + "fmt" + "time" + "code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto" "code.smartsheep.studio/hydrogen/interactive/pkg/database" "code.smartsheep.studio/hydrogen/interactive/pkg/models" - "errors" - "fmt" - pluralize "github.com/gertd/go-pluralize" "github.com/rs/zerolog/log" "github.com/samber/lo" "github.com/spf13/viper" "gorm.io/gorm" - "strings" - "time" ) -type PostTypeContext[T models.PostInterface] struct { +type PostTypeContext struct { Tx *gorm.DB - TypeName string - CanReply bool - CanRepost bool + TableName string + ColumnName string + CanReply bool + CanRepost bool } -var pluralizeHelper = pluralize.NewClient() - -func (v *PostTypeContext[T]) GetTableName(plural ...bool) string { - if len(plural) <= 0 || !plural[0] { - return strings.ToLower(v.TypeName) - } else { - return pluralizeHelper.Plural(strings.ToLower(v.TypeName)) - } -} - -func (v *PostTypeContext[T]) Preload(noComments ...bool) *PostTypeContext[T] { - v.Tx.Preload("Author"). - Preload("Attachments"). - Preload("Categories"). - Preload("Hashtags") - - if len(noComments) <= 0 || !noComments[0] { - v.Tx = v.Tx.Preload("Comments") - } - - if v.CanReply { - v.Tx.Preload("ReplyTo") - } - if v.CanRepost { - v.Tx.Preload("RepostTo") - } - +func (v *PostTypeContext) FilterWithCategory(alias string) *PostTypeContext { + name := v.ColumnName + v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", name, v.TableName, name, name)). + Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", name, name, name)). + Where(name+"_categories.alias = ?", alias) return v } -func (v *PostTypeContext[T]) FilterWithCategory(alias string) *PostTypeContext[T] { - table := v.GetTableName() - v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", table, v.GetTableName(true), table, v.GetTableName())). - Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", table, table, table)). - Where(table+"_categories.alias = ?", alias) +func (v *PostTypeContext) FilterWithTag(alias string) *PostTypeContext { + name := v.ColumnName + v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", name, v.TableName, name, name)). + Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", name, name, name)). + Where(name+"_tags.alias = ?", alias) return v } -func (v *PostTypeContext[T]) FilterWithTag(alias string) *PostTypeContext[T] { - table := v.GetTableName() - v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", table, v.GetTableName(true), table, v.GetTableName())). - Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", table, table, table)). - Where(table+"_tags.alias = ?", alias) - return v -} - -func (v *PostTypeContext[T]) FilterPublishedAt(date time.Time) *PostTypeContext[T] { +func (v *PostTypeContext) FilterPublishedAt(date time.Time) *PostTypeContext { v.Tx.Where("published_at <= ? AND published_at IS NULL", date) return v } -func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { +func (v *PostTypeContext) FilterRealm(id uint) *PostTypeContext { if id > 0 { v.Tx = v.Tx.Where("realm_id = ?", id) } else { @@ -84,12 +53,12 @@ func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] { return v } -func (v *PostTypeContext[T]) FilterAuthor(id uint) *PostTypeContext[T] { +func (v *PostTypeContext) FilterAuthor(id uint) *PostTypeContext { v.Tx = v.Tx.Where("author_id = ?", id) return v } -func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { +func (v *PostTypeContext) FilterReply(condition bool) *PostTypeContext { if condition { v.Tx = v.Tx.Where("reply_id IS NOT NULL") } else { @@ -98,32 +67,34 @@ func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] { return v } -func (v *PostTypeContext[T]) SortCreatedAt(order string) *PostTypeContext[T] { +func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext { v.Tx.Order(fmt.Sprintf("created_at %s", order)) return v } -func (v *PostTypeContext[T]) GetViaAlias(alias string, noComments ...bool) (T, error) { - var item T - if err := v.Preload(noComments...).Tx.Where("alias = ?", alias).First(&item).Error; err != nil { +func (v *PostTypeContext) GetViaAlias(alias string, noComments ...bool) (models.Feed, error) { + var item models.Feed + if err := v.Tx.Where("alias = ?", alias).First(&item).Error; err != nil { return item, err } return item, nil } -func (v *PostTypeContext[T]) Get(id uint, noComments ...bool) (T, error) { - var item T - if err := v.Preload(noComments...).Tx.Where("id = ?", id).First(&item).Error; err != nil { +func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) { + var item models.Feed + if err := v.Tx. + Select("*, ? as model_type", v.ColumnName). + Where("id = ?", id).First(&item).Error; err != nil { return item, err } return item, nil } -func (v *PostTypeContext[T]) Count() (int64, error) { +func (v *PostTypeContext) Count() (int64, error) { var count int64 - table := viper.GetString("database.prefix") + v.GetTableName(true) + table := viper.GetString("database.prefix") + v.TableName if err := v.Tx.Table(table).Count(&count).Error; err != nil { return count, err } @@ -131,7 +102,7 @@ func (v *PostTypeContext[T]) Count() (int64, error) { return count, nil } -func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { +func (v *PostTypeContext) CountReactions(id uint) (map[string]int64, error) { var reactions []struct { Symbol string Count int64 @@ -139,7 +110,7 @@ func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { if err := database.C.Model(&models.Reaction{}). Select("symbol, COUNT(id) as count"). - Where(strings.ToLower(v.TypeName)+"_id = ?", id). + Where(v.ColumnName+"_id = ?", id). Group("symbol"). Scan(&reactions).Error; err != nil { return map[string]int64{}, err @@ -148,23 +119,26 @@ func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) { return lo.SliceToMap(reactions, func(item struct { Symbol string Count int64 - }) (string, int64) { + }, + ) (string, int64) { return item.Symbol, item.Count }), nil } -func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, error) { +func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models.Feed, error) { if take > 20 { take = 20 } - var items []T - if err := v.Preload().Tx.Limit(take).Offset(offset).Find(&items).Error; err != nil { + var items []*models.Feed + if err := v.Tx. + Select("*, ? as model_type", v.ColumnName). + Limit(take).Offset(offset).Find(&items).Error; err != nil { return items, err } - idx := lo.Map(items, func(item T, index int) uint { - return item.GetID() + idx := lo.Map(items, func(item *models.Feed, index int) uint { + return item.ID }) if len(noReact) <= 0 || !noReact[0] { @@ -175,15 +149,15 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e } if err := database.C.Model(&models.Reaction{}). - Select(strings.ToLower(v.TypeName)+"_id as post_id, symbol, COUNT(id) as count"). - Where(strings.ToLower(v.TypeName)+"_id IN (?)", idx). + Select(v.ColumnName+"_id as post_id, symbol, COUNT(id) as count"). + Where(v.ColumnName+"_id IN (?)", idx). Group("post_id, symbol"). Scan(&reactions).Error; err != nil { return items, err } - itemMap := lo.SliceToMap(items, func(item T) (uint, T) { - return item.GetID(), item + itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) { + return item.ID, item }) list := map[uint]map[string]int64{} @@ -196,7 +170,7 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e for k, v := range list { if post, ok := itemMap[k]; ok { - post.SetReactionList(v) + post.ReactionList = v } } } @@ -204,7 +178,7 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e return items, nil } -func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { +func MapCategoriesAndTags[T models.PostInterface](item T) (T, error) { var err error categories := item.GetCategories() for idx, category := range categories { @@ -225,8 +199,8 @@ func (v *PostTypeContext[T]) MapCategoriesAndTags(item T) (T, error) { return item, nil } -func (v *PostTypeContext[T]) New(item T) (T, error) { - item, err := v.MapCategoriesAndTags(item) +func NewPost[T models.PostInterface](item T) (T, error) { + item, err := MapCategoriesAndTags(item) if err != nil { return item, err } @@ -294,8 +268,8 @@ func (v *PostTypeContext[T]) New(item T) (T, error) { return item, nil } -func (v *PostTypeContext[T]) Edit(item T) (T, error) { - item, err := v.MapCategoriesAndTags(item) +func EditPost[T models.PostInterface](item T) (T, error) { + item, err := MapCategoriesAndTags(item) if err != nil { return item, err } @@ -305,11 +279,11 @@ func (v *PostTypeContext[T]) Edit(item T) (T, error) { return item, err } -func (v *PostTypeContext[T]) Delete(item T) error { +func DeletePost[T models.PostInterface](item T) error { return database.C.Delete(&item).Error } -func (v *PostTypeContext[T]) React(reaction models.Reaction) (bool, models.Reaction, error) { +func (v *PostTypeContext) React(reaction models.Reaction) (bool, models.Reaction, error) { if err := database.C.Where(reaction).First(&reaction).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return true, reaction, database.C.Save(&reaction).Error diff --git a/pkg/views/src/components/posts/MomentContent.vue b/pkg/views/src/components/posts/MomentContent.vue index f93cff4..22c07c4 100644 --- a/pkg/views/src/components/posts/MomentContent.vue +++ b/pkg/views/src/components/posts/MomentContent.vue @@ -14,7 +14,7 @@ function parseContent(src: string): string {