⚗️ Testing with the new post listing v2

This commit is contained in:
2025-03-29 22:38:43 +08:00
parent 7a7d57c0d7
commit 3cd102046a
10 changed files with 220 additions and 21 deletions

View File

@ -20,7 +20,7 @@ func getCategory(c *fiber.Ctx) error {
}
func listCategories(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
probe := c.Query("probe")

View File

@ -59,6 +59,7 @@ func MapControllers(app *fiber.App, baseURL string) {
posts := api.Group("/posts").Name("Posts API")
{
posts.Get("/", listPost)
posts.Get("/v2", listPostV2)
posts.Get("/search", searchPost)
posts.Get("/minimal", listPostMinimal)
posts.Get("/drafts", listDraftPost)

View File

@ -15,6 +15,7 @@ import (
"git.solsynth.dev/hypernet/interactive/pkg/internal/http/exts"
"git.solsynth.dev/hypernet/interactive/pkg/internal/models"
"git.solsynth.dev/hypernet/interactive/pkg/internal/services"
"git.solsynth.dev/hypernet/interactive/pkg/internal/services/queries"
"github.com/gofiber/fiber/v2"
"github.com/samber/lo"
)
@ -63,7 +64,7 @@ func getPost(c *fiber.Ctx) error {
}
func searchPost(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C
@ -113,7 +114,7 @@ func searchPost(c *fiber.Ctx) error {
}
func listPost(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C
@ -153,8 +154,47 @@ func listPost(c *fiber.Ctx) error {
})
}
func listPostV2(c *fiber.Ctx) error {
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C
var err error
if tx, err = services.UniversalPostFilter(c, tx); err != nil {
return err
}
var userId *uint
if user, authenticated := c.Locals("user").(authm.Account); authenticated {
userId = &user.ID
}
countTx := tx
count, err := services.CountPost(countTx)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
items, err := queries.ListPostV2(tx, take, offset, "published_at DESC", userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
if c.QueryBool("truncate", true) {
for _, item := range items {
item = services.TruncatePostContent(item)
}
}
return c.JSON(fiber.Map{
"count": count,
"data": items,
})
}
func listPostMinimal(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C
@ -190,7 +230,7 @@ func listPostMinimal(c *fiber.Ctx) error {
}
func listDraftPost(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
if err := sec.EnsureAuthenticated(c); err != nil {

View File

@ -46,7 +46,7 @@ func listRecommendation(c *fiber.Ctx) error {
}
func listRecommendationShuffle(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C

View File

@ -11,7 +11,7 @@ import (
)
func listPostReplies(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
tx := database.C
@ -59,7 +59,7 @@ func listPostReplies(c *fiber.Ctx) error {
}
func listPostFeaturedReply(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
take = max(1, min(take, 3))
var userId *uint

View File

@ -18,7 +18,7 @@ func getTag(c *fiber.Ctx) error {
}
func listTags(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
take := c.QueryInt("take", 10)
offset := c.QueryInt("offset", 0)
probe := c.Query("probe")

View File

@ -1,6 +1,9 @@
package models
import "git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
import (
"git.solsynth.dev/hypernet/nexus/pkg/nex/cruda"
"git.solsynth.dev/hypernet/passport/pkg/authkit/models"
)
const (
PublisherTypePersonal = iota
@ -26,4 +29,7 @@ type Publisher struct {
RealmID *uint `json:"realm_id"`
AccountID *uint `json:"account_id"`
Account models.Account `gorm:"-"`
Realm models.Realm `json:"-"`
}

View File

@ -0,0 +1,136 @@
package queries
import (
"fmt"
"git.solsynth.dev/hypernet/interactive/pkg/internal/database"
"git.solsynth.dev/hypernet/interactive/pkg/internal/gap"
"git.solsynth.dev/hypernet/interactive/pkg/internal/models"
"git.solsynth.dev/hypernet/interactive/pkg/internal/services"
"git.solsynth.dev/hypernet/paperclip/pkg/filekit"
fmodels "git.solsynth.dev/hypernet/paperclip/pkg/filekit/models"
"git.solsynth.dev/hypernet/passport/pkg/authkit"
amodels "git.solsynth.dev/hypernet/passport/pkg/authkit/models"
"github.com/samber/lo"
"gorm.io/gorm"
)
// This api still is experimental and finally with replace the old one
// Some changes between ListPost and ListPostV2:
// - Post reply to and repost to are not included
func ListPostV2(tx *gorm.DB, take int, offset int, order any, user *uint) ([]models.Post, error) {
if take > 100 {
take = 100
}
if take >= 0 {
tx = tx.Limit(take)
}
if offset >= 0 {
tx = tx.Offset(offset)
}
tx = tx.Preload("Tags").
Preload("Categories").
Preload("Publisher")
// Fetch posts
var posts []models.Post
if err := tx.Order(order).Find(&posts).Error; err != nil {
return nil, err
}
// If no posts found, return early
if len(posts) == 0 {
return posts, nil
}
// Collect post IDs
idx := make([]uint, len(posts))
itemMap := make(map[uint]*models.Post, len(posts))
for i, item := range posts {
idx[i] = item.ID
itemMap[item.ID] = &item
}
// Batch load reactions
if mapping, err := services.BatchListPostReactions(database.C.Where("post_id IN ?", idx), "post_id"); err != nil {
return posts, err
} else {
for postID, reactions := range mapping {
if post, exists := itemMap[postID]; exists {
post.Metric.ReactionList = reactions
}
}
}
// Batch load reply counts efficiently
var replies []struct {
PostID uint
Count int64
}
if err := database.C.Model(&models.Post{}).
Select("reply_id as post_id, COUNT(id) as count").
Where("reply_id IN (?)", idx).
Group("post_id").
Find(&replies).Error; err != nil {
return posts, err
}
for _, info := range replies {
if post, exists := itemMap[info.PostID]; exists {
post.Metric.ReplyCount = info.Count
}
}
// Batch load some metadata
var attachmentsRid []string
var usersId []uint
// Scan records that can be load egearly
for _, info := range posts {
if val, ok := info.Body["attachments"].([]string); ok && len(val) > 0 {
if info.Publisher.AccountID != nil {
usersId = append(usersId, *info.Publisher.AccountID)
}
attachmentsRid = append(attachmentsRid, val...)
}
}
// Batch load attachments
attachmentsRid = lo.Uniq(attachmentsRid)
attachments, err := filekit.ListAttachment(gap.Nx, attachmentsRid)
if err != nil {
return posts, fmt.Errorf("failed to load attachments: %v", err)
}
// Batch load publisher users
usersId = lo.Uniq(usersId)
users, err := authkit.ListUser(gap.Nx, usersId)
if err != nil {
return posts, fmt.Errorf("failed to load users: %v", err)
}
// Putting information back to data
for _, item := range posts {
var this []fmodels.Attachment
if val, ok := item.Body["attachments"].([]string); ok && len(val) > 0 {
this = lo.Filter(attachments, func(item fmodels.Attachment, _ int) bool {
return lo.Contains(val, item.Rid)
})
}
item.Body["attachments"] = this
item.Publisher.Account = lo.FindOrElse(users, amodels.Account{}, func(acc amodels.Account) bool {
if item.Publisher.AccountID == nil {
return false
}
return acc.ID == *item.Publisher.AccountID
})
}
// Add post views for the user
if user != nil {
services.AddPostViews(posts, *user)
}
return posts, nil
}