diff --git a/pkg/internal/models/posts.go b/pkg/internal/models/posts.go index 8e6eec2..7aca54f 100644 --- a/pkg/internal/models/posts.go +++ b/pkg/internal/models/posts.go @@ -26,7 +26,7 @@ type Post struct { hyper.BaseModel Type string `json:"type"` - Body datatypes.JSONMap `json:"body"` + Body datatypes.JSONMap `json:"body" gorm:"index:,type:gin"` Language string `json:"language"` Alias *string `json:"alias"` AreaAlias *string `json:"area_alias"` diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index 178d3ba..bfaa696 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -34,6 +34,7 @@ func MapAPIs(app *fiber.App, baseURL string) { posts := api.Group("/posts").Name("Posts API") { posts.Get("/", listPost) + posts.Get("/search", searchPost) posts.Get("/minimal", listPostMinimal) posts.Get("/drafts", listDraftPost) posts.Get("/:postId", getPost) diff --git a/pkg/internal/server/api/posts_api.go b/pkg/internal/server/api/posts_api.go index c23646d..1df9033 100644 --- a/pkg/internal/server/api/posts_api.go +++ b/pkg/internal/server/api/posts_api.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "gorm.io/gorm" "strconv" "strings" @@ -15,6 +16,47 @@ import ( "github.com/samber/lo" ) +func universalPostFilter(c *fiber.Ctx, tx *gorm.DB) (*gorm.DB, error) { + realm := c.Query("realm") + + tx = services.FilterPostDraft(tx) + + if user, authenticated := c.Locals("user").(models.Account); authenticated { + tx = services.FilterPostWithUserContext(tx, &user) + } else { + tx = services.FilterPostWithUserContext(tx, nil) + } + + if len(realm) > 0 { + if realm, err := services.GetRealmWithAlias(realm); err != nil { + return tx, fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) + } else { + tx = services.FilterPostWithRealm(tx, realm.ID) + } + } + + if c.QueryBool("noReply", true) { + tx = services.FilterPostReply(tx) + } + + if len(c.Query("author")) > 0 { + var author models.Account + if err := database.C.Where(&hyper.BaseUser{Name: c.Query("author")}).First(&author).Error; err != nil { + return tx, fiber.NewError(fiber.StatusNotFound, err.Error()) + } + tx = tx.Where("author_id = ?", author.ID) + } + + if len(c.Query("category")) > 0 { + tx = services.FilterPostWithCategory(tx, c.Query("category")) + } + if len(c.Query("tag")) > 0 { + tx = services.FilterPostWithTag(tx, c.Query("tag")) + } + + return tx, nil +} + func getPost(c *fiber.Ctx) error { id := c.Params("postId") @@ -57,44 +99,58 @@ func getPost(c *fiber.Ctx) error { return c.JSON(item) } +func searchPost(c *fiber.Ctx) error { + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + + tx := database.C + + probe := c.Query("probe") + if len(probe) == 0 { + return fiber.NewError(fiber.StatusBadRequest, "probe is required") + } + + tx = services.FilterPostWithFuzzySearch(tx, probe) + + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err + } + + countTx := tx + count, err := services.CountPost(countTx) + if err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + items, err := services.ListPost(tx, take, offset, "published_at DESC") + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + if c.QueryBool("truncate", true) { + for _, item := range items { + if item != nil { + item = lo.ToPtr(services.TruncatePostContent(*item)) + } + } + } + + return c.JSON(fiber.Map{ + "count": count, + "data": items, + }) +} + func listPost(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) - realm := c.Query("realm") - tx := services.FilterPostDraft(database.C) + tx := database.C - if user, authenticated := c.Locals("user").(models.Account); authenticated { - tx = services.FilterPostWithUserContext(tx, &user) - } else { - tx = services.FilterPostWithUserContext(tx, nil) - } - - if len(realm) > 0 { - if realm, err := services.GetRealmWithAlias(realm); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterPostWithRealm(tx, realm.ID) - } - } - - if c.QueryBool("noReply", true) { - tx = services.FilterPostReply(tx) - } - - if len(c.Query("author")) > 0 { - var author models.Account - if err := database.C.Where(&hyper.BaseUser{Name: c.Query("author")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - tx = tx.Where("author_id = ?", author.ID) - } - - if len(c.Query("category")) > 0 { - tx = services.FilterPostWithCategory(tx, c.Query("category")) - } - if len(c.Query("tag")) > 0 { - tx = services.FilterPostWithTag(tx, c.Query("tag")) + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err } countTx := tx @@ -125,37 +181,12 @@ func listPost(c *fiber.Ctx) error { func listPostMinimal(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) - realm := c.Query("realm") - tx := services.FilterPostDraft(database.C) + tx := database.C - if user, authenticated := c.Locals("user").(models.Account); authenticated { - tx = services.FilterPostWithUserContext(tx, &user) - } else { - tx = services.FilterPostWithUserContext(tx, nil) - } - - if len(realm) > 0 { - if realm, err := services.GetRealmWithAlias(realm); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterPostWithRealm(tx, realm.ID) - } - } - - if len(c.Query("author")) > 0 { - var author models.Account - if err := database.C.Where(&hyper.BaseUser{Name: c.Query("author")}).First(&author).Error; err != nil { - return fiber.NewError(fiber.StatusNotFound, err.Error()) - } - tx = tx.Where("author_id = ?", author.ID) - } - - if len(c.Query("category")) > 0 { - tx = services.FilterPostWithCategory(tx, c.Query("category")) - } - if len(c.Query("tag")) > 0 { - tx = services.FilterPostWithTag(tx, c.Query("tag")) + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err } countTx := tx diff --git a/pkg/internal/server/api/recommendation_api.go b/pkg/internal/server/api/recommendation_api.go index 8103605..dfb514c 100644 --- a/pkg/internal/server/api/recommendation_api.go +++ b/pkg/internal/server/api/recommendation_api.go @@ -1,8 +1,6 @@ package api import ( - "fmt" - "git.solsynth.dev/hydrogen/interactive/pkg/internal/database" "git.solsynth.dev/hydrogen/interactive/pkg/internal/gap" "git.solsynth.dev/hydrogen/interactive/pkg/internal/models" @@ -14,26 +12,12 @@ import ( func listRecommendationNews(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) - realm := c.Query("realm") - tx := services.FilterPostDraft(database.C) + tx := database.C - if user, authenticated := c.Locals("user").(models.Account); authenticated { - tx = services.FilterPostWithUserContext(tx, &user) - } else { - tx = services.FilterPostWithUserContext(tx, nil) - } - - if len(realm) > 0 { - if realm, err := services.GetRealmWithAlias(realm); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterPostWithRealm(tx, realm.ID) - } - } - - if c.QueryBool("noReply", true) { - tx = services.FilterPostReply(tx) + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err } countTx := tx @@ -74,10 +58,13 @@ func listRecommendationFriends(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) - realm := c.Query("realm") - tx := services.FilterPostDraft(database.C) - tx = services.FilterPostWithUserContext(tx, &user) + tx := database.C + + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err + } friends, _ := services.ListAccountFriends(user) friendList := lo.Map(friends, func(item models.Account, index int) uint { @@ -86,18 +73,6 @@ func listRecommendationFriends(c *fiber.Ctx) error { tx = tx.Where("author_id IN ?", friendList) - if len(realm) > 0 { - if realm, err := services.GetRealmWithAlias(realm); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterPostWithRealm(tx, realm.ID) - } - } - - if c.QueryBool("noReply", true) { - tx = services.FilterPostReply(tx) - } - countTx := tx count, err := services.CountPost(countTx) if err != nil { @@ -131,26 +106,12 @@ func listRecommendationFriends(c *fiber.Ctx) error { func listRecommendationShuffle(c *fiber.Ctx) error { take := c.QueryInt("take", 0) offset := c.QueryInt("offset", 0) - realm := c.Query("realm") - tx := services.FilterPostDraft(database.C) + tx := database.C - if user, authenticated := c.Locals("user").(models.Account); authenticated { - tx = services.FilterPostWithUserContext(tx, &user) - } else { - tx = services.FilterPostWithUserContext(tx, nil) - } - - if len(realm) > 0 { - if realm, err := services.GetRealmWithAlias(realm); err != nil { - return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err)) - } else { - tx = services.FilterPostWithRealm(tx, realm.ID) - } - } - - if c.QueryBool("noReply", true) { - tx = services.FilterPostReply(tx) + var err error + if tx, err = universalPostFilter(c, tx); err != nil { + return err } countTx := tx diff --git a/pkg/internal/services/posts.go b/pkg/internal/services/posts.go index a1499f9..5dee170 100644 --- a/pkg/internal/services/posts.go +++ b/pkg/internal/services/posts.go @@ -97,6 +97,11 @@ func FilterPostDraft(tx *gorm.DB) *gorm.DB { return tx.Where("is_draft = ? OR is_draft IS NULL", false) } +func FilterPostWithFuzzySearch(tx *gorm.DB, probe string) *gorm.DB { + probe = "%" + probe + "%" + return tx.Where("? AND body->>'content' ILIKE ?", gorm.Expr("body ? 'content'"), probe) +} + func PreloadGeneral(tx *gorm.DB) *gorm.DB { return tx. Preload("Tags").