diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 35410ca..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# 默认忽略的文件
-/shelf/
-/workspace.xml
-# 基于编辑器的 HTTP 客户端请求
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/Interactive.iml b/.idea/Interactive.iml
deleted file mode 100644
index 5e764c4..0000000
--- a/.idea/Interactive.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
deleted file mode 100644
index c4444d9..0000000
--- a/.idea/dataSources.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
- postgresql
- true
- org.postgresql.Driver
- jdbc:postgresql://localhost:5432/hy_interactive
- $ProjectFileDir$
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 03d9549..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 79e159c..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/pkg/internal/http/api/activitypub_api.go b/pkg/internal/http/api/activitypub_api.go
index 5972334..c182025 100644
--- a/pkg/internal/http/api/activitypub_api.go
+++ b/pkg/internal/http/api/activitypub_api.go
@@ -65,7 +65,7 @@ func apUserOutbox(c *fiber.Ctx) error {
}
var activities []activitypub.Item
- if posts, err := services.ListPost(tx, limit, (page-1)*limit, "published_at DESC", nil); err != nil {
+ if posts, err := services.ListPostV1(tx, limit, (page-1)*limit, "published_at DESC", nil); err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
} else {
for _, post := range posts {
diff --git a/pkg/internal/http/api/index.go b/pkg/internal/http/api/index.go
index fd02cd2..28ebeba 100644
--- a/pkg/internal/http/api/index.go
+++ b/pkg/internal/http/api/index.go
@@ -59,7 +59,6 @@ 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)
diff --git a/pkg/internal/http/api/posts_api.go b/pkg/internal/http/api/posts_api.go
index 5e71a6b..956cb0f 100644
--- a/pkg/internal/http/api/posts_api.go
+++ b/pkg/internal/http/api/posts_api.go
@@ -26,17 +26,26 @@ func getPost(c *fiber.Ctx) error {
var item models.Post
var err error
- tx := database.C
+ var userId *uint
+ if user, authenticated := c.Locals("user").(authm.Account); authenticated {
+ userId = &user.ID
+ }
+ tx := database.C
if tx, err = services.UniversalPostFilter(c, tx, services.UniversalPostFilterConfig{
- ShowReply: true,
- ShowDraft: true,
+ ShowReply: true,
+ ShowDraft: true,
+ ShowCollapsed: true,
}); err != nil {
return err
}
if numericId, paramErr := strconv.Atoi(id); paramErr == nil {
- item, err = services.GetPost(tx, uint(numericId))
+ if c.Get("X-API-Version", "1") == "2" {
+ item, err = queries.GetPost(tx, uint(numericId), userId)
+ } else {
+ item, err = services.GetPost(tx, uint(numericId))
+ }
} else {
segments := strings.Split(id, ":")
if len(segments) != 2 {
@@ -44,7 +53,11 @@ func getPost(c *fiber.Ctx) error {
}
area := segments[0]
alias := segments[1]
- item, err = services.GetPostByAlias(tx, alias, area)
+ if c.Get("X-API-Version", "1") == "2" {
+ item, err = queries.GetPostByAlias(tx, alias, area, userId)
+ } else {
+ item, err = services.GetPostByAlias(tx, alias, area)
+ }
}
if err != nil {
@@ -88,22 +101,27 @@ func searchPost(c *fiber.Ctx) error {
userId = &user.ID
}
+ var count int64
countTx := tx
- count, err := services.CountPost(countTx)
+ 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", userId)
+ var items []models.Post
+
+ if c.Get("X-API-Version", "1") == "2" {
+ items, err = queries.ListPost(tx, take, offset, "published_at DESC", userId)
+ } else {
+ items, err = services.ListPostV1(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 {
- if item != nil {
- item = lo.ToPtr(services.TruncatePostContent(*item))
- }
+ item = services.TruncatePostContent(item)
}
}
@@ -129,54 +147,20 @@ func listPost(c *fiber.Ctx) error {
userId = &user.ID
}
+ var count int64
countTx := tx
- count, err := services.CountPost(countTx)
+ 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", userId)
- if err != nil {
- return fiber.NewError(fiber.StatusBadRequest, err.Error())
+ var items []models.Post
+
+ if c.Get("X-API-Version", "1") == "2" {
+ items, err = queries.ListPost(tx, take, offset, "published_at DESC", userId)
+ } else {
+ items, err = services.ListPostV1(tx, take, offset, "published_at DESC", userId)
}
-
- 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 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())
}
@@ -238,6 +222,7 @@ func listDraftPost(c *fiber.Ctx) error {
}
user := c.Locals("user").(authm.Account)
+ var err error
tx := services.FilterPostWithAuthorDraft(database.C, user.ID)
var userId *uint
@@ -245,21 +230,27 @@ func listDraftPost(c *fiber.Ctx) error {
userId = &user.ID
}
- count, err := services.CountPost(tx)
+ var count int64
+ countTx := tx
+ count, err = services.CountPost(countTx)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
- items, err := services.ListPost(tx, take, offset, "created_at DESC", userId, true)
+ var items []models.Post
+
+ if c.Get("X-API-Version", "1") == "2" {
+ items, err = queries.ListPost(tx, take, offset, "published_at DESC", userId)
+ } else {
+ items, err = services.ListPostV1(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 {
- if item != nil {
- item = lo.ToPtr(services.TruncatePostContent(*item))
- }
+ item = services.TruncatePostContent(item)
}
}
diff --git a/pkg/internal/http/api/publishers_api.go b/pkg/internal/http/api/publishers_api.go
index 0ac4d1f..a2c8d1d 100644
--- a/pkg/internal/http/api/publishers_api.go
+++ b/pkg/internal/http/api/publishers_api.go
@@ -33,7 +33,7 @@ func listPinnedPost(c *fiber.Ctx) error {
userId = &user.ID
}
- items, err := services.ListPost(tx, 100, 0, "published_at DESC", userId)
+ items, err := services.ListPostV1(tx, 100, 0, "published_at DESC", userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
diff --git a/pkg/internal/http/api/recommendation_api.go b/pkg/internal/http/api/recommendation_api.go
index 7cdd204..cc6a071 100644
--- a/pkg/internal/http/api/recommendation_api.go
+++ b/pkg/internal/http/api/recommendation_api.go
@@ -29,7 +29,7 @@ func listRecommendation(c *fiber.Ctx) error {
}
tx := database.C.Where("id IN ?", postIdx)
- newPosts, err := services.ListPost(tx, featuredMax, 0, "id ASC", userId)
+ newPosts, err := services.ListPostV1(tx, featuredMax, 0, "id ASC", userId)
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
@@ -67,7 +67,7 @@ func listRecommendationShuffle(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
- items, err := services.ListPost(tx, take, offset, "RANDOM()", userId)
+ items, err := services.ListPostV1(tx, take, offset, "RANDOM()", userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@@ -92,7 +92,7 @@ func getRecommendationFeed(c *fiber.Ctx) error {
var cursorTime *time.Time
if cursor > 0 {
- cursorTime = lo.ToPtr(time.UnixMilli(int64(cursor) + 1))
+ cursorTime = lo.ToPtr(time.UnixMilli(int64(cursor - 1)))
}
var userId *uint
diff --git a/pkg/internal/http/api/replies_api.go b/pkg/internal/http/api/replies_api.go
index c0b73da..25d13e9 100644
--- a/pkg/internal/http/api/replies_api.go
+++ b/pkg/internal/http/api/replies_api.go
@@ -47,7 +47,7 @@ func listPostReplies(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
- items, err := services.ListPost(tx, take, offset, "published_at DESC", userId)
+ items, err := services.ListPostV1(tx, take, offset, "published_at DESC", userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@@ -90,7 +90,7 @@ func listPostFeaturedReply(c *fiber.Ctx) error {
tx = services.FilterPostWithTag(tx, c.Query("tag"))
}
- items, err := services.ListPost(tx, take, 0, "(COALESCE(total_upvote, 0) - COALESCE(total_downvote, 0)) DESC, published_at DESC", userId)
+ items, err := services.ListPostV1(tx, take, 0, "(COALESCE(total_upvote, 0) - COALESCE(total_downvote, 0)) DESC, published_at DESC", userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
diff --git a/pkg/internal/http/api/what_new_api.dart.go b/pkg/internal/http/api/what_new_api.dart.go
index 3431f82..e7b9e05 100644
--- a/pkg/internal/http/api/what_new_api.dart.go
+++ b/pkg/internal/http/api/what_new_api.dart.go
@@ -40,7 +40,7 @@ func getWhatsNew(c *fiber.Ctx) error {
order = "published_at DESC, (COALESCE(total_upvote, 0) - COALESCE(total_downvote, 0)) DESC"
}
- items, err := services.ListPost(tx, 10, 0, order, userId)
+ items, err := services.ListPostV1(tx, 10, 0, order, userId)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
diff --git a/pkg/internal/services/feed.go b/pkg/internal/services/feed.go
index 48b8000..1ff8b80 100644
--- a/pkg/internal/services/feed.go
+++ b/pkg/internal/services/feed.go
@@ -79,14 +79,14 @@ func GetFeed(c *fiber.Ctx, limit int, user *uint, cursor *time.Time) ([]FeedEntr
// Only manage to pulling the content only
func ListPostForFeed(tx *gorm.DB, limit int, user *uint) ([]FeedEntry, error) {
- posts, err := ListPost(tx, limit, -1, "published_at DESC", user)
+ posts, err := ListPostV1(tx, limit, -1, "published_at DESC", user)
if err != nil {
return nil, err
}
- entries := lo.Map(posts, func(post *models.Post, _ int) FeedEntry {
+ entries := lo.Map(posts, func(post models.Post, _ int) FeedEntry {
return FeedEntry{
Type: "interactive.post",
- Data: TruncatePostContent(*post),
+ Data: TruncatePostContent(post),
CreatedAt: post.CreatedAt,
}
})
diff --git a/pkg/internal/services/posts.go b/pkg/internal/services/posts.go
index 149f3e5..a0bcd09 100644
--- a/pkg/internal/services/posts.go
+++ b/pkg/internal/services/posts.go
@@ -339,14 +339,7 @@ func PreloadGeneral(tx *gorm.DB) *gorm.DB {
Preload("Tags").
Preload("Categories").
Preload("Publisher").
- Preload("ReplyTo").
- Preload("ReplyTo.Publisher").
- Preload("ReplyTo.Tags").
- Preload("ReplyTo.Categories").
- Preload("RepostTo").
- Preload("RepostTo.Publisher").
- Preload("RepostTo.Tags").
- Preload("RepostTo.Categories")
+ Preload("Poll")
}
func GetPost(tx *gorm.DB, id uint) (models.Post, error) {
@@ -403,7 +396,7 @@ func CountPostReactions(id uint) int64 {
return count
}
-func ListPost(tx *gorm.DB, take int, offset int, order any, user *uint, noReact ...bool) ([]*models.Post, error) {
+func ListPostV1(tx *gorm.DB, take int, offset int, order any, user *uint, noReact ...bool) ([]models.Post, error) {
if take > 100 {
take = 100
}
@@ -415,77 +408,64 @@ func ListPost(tx *gorm.DB, take int, offset int, order any, user *uint, noReact
tx = tx.Offset(offset)
}
- var items []*models.Post
- if err := PreloadGeneral(tx).
- Order(order).
- Find(&items).Error; err != nil {
- return items, err
+ 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
}
- idx := lo.Map(items, func(item *models.Post, index int) uint {
- return item.ID
- })
+ // If no posts found, return early
+ if len(posts) == 0 {
+ return posts, nil
+ }
- // Load reactions
- if len(noReact) <= 0 || !noReact[0] {
- if mapping, err := BatchListPostReactions(database.C.Where("post_id IN ?", idx), "post_id"); err != nil {
- return items, err
- } else {
- itemMap := lo.SliceToMap(items, func(item *models.Post) (uint, *models.Post) {
- return item.ID, item
- })
+ // 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
+ }
- for k, v := range mapping {
- if post, ok := itemMap[k]; ok {
- post.Metric = models.PostMetric{
- ReactionList: v,
- }
- }
+ // Batch load reactions
+ if mapping, err := 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
}
}
}
- // Load replies
- if len(noReact) <= 0 || !noReact[0] {
- 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").
- Scan(&replies).Error; err != nil {
- return items, err
- }
-
- itemMap := lo.SliceToMap(items, func(item *models.Post) (uint, *models.Post) {
- return item.ID, item
- })
-
- list := map[uint]int64{}
- for _, info := range replies {
- list[info.PostID] = info.Count
- }
-
- for k, v := range list {
- if post, ok := itemMap[k]; ok {
- post.Metric = models.PostMetric{
- ReactionList: post.Metric.ReactionList,
- ReplyCount: v,
- }
- }
+ // 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
}
}
+ // Add post views for the user
if user != nil {
- AddPostViews(lo.Map(items, func(item *models.Post, index int) models.Post {
- return *item
- }), *user)
+ AddPostViews(posts, *user)
}
- return items, nil
+ return posts, nil
}
func ListPostMinimal(tx *gorm.DB, take int, offset int, order any) ([]*models.Post, error) {
diff --git a/pkg/internal/services/queries/posts.go b/pkg/internal/services/queries/posts.go
index 6266f7c..c496bd9 100644
--- a/pkg/internal/services/queries/posts.go
+++ b/pkg/internal/services/queries/posts.go
@@ -2,6 +2,7 @@ package queries
import (
"fmt"
+
"github.com/goccy/go-json"
"git.solsynth.dev/hypernet/interactive/pkg/internal/database"
@@ -19,47 +20,18 @@ import (
var singularAttachmentFields = []string{"video", "thumbnail"}
-// 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
- }
-
+func CompletePostMeta(in ...models.Post) ([]models.Post, error) {
// Collect post IDs
- idx := make([]uint, len(posts))
- itemMap := make(map[uint]*models.Post, len(posts))
- for i, item := range posts {
+ idx := make([]uint, len(in))
+ itemMap := make(map[uint]*models.Post, len(in))
+ for i, item := range in {
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
+ return in, err
} else {
for postID, reactions := range mapping {
if post, exists := itemMap[postID]; exists {
@@ -78,7 +50,7 @@ func ListPostV2(tx *gorm.DB, take int, offset int, order any, user *uint) ([]mod
Where("reply_id IN (?)", idx).
Group("post_id").
Find(&replies).Error; err != nil {
- return posts, err
+ return in, err
}
for _, info := range replies {
if post, exists := itemMap[info.PostID]; exists {
@@ -93,12 +65,12 @@ func ListPostV2(tx *gorm.DB, take int, offset int, order any, user *uint) ([]mod
// Scan records that can be load eagerly
var bodies []models.PostStoryBody
{
- raw, _ := json.Marshal(lo.Map(posts, func(item models.Post, _ int) map[string]any {
+ raw, _ := json.Marshal(lo.Map(in, func(item models.Post, _ int) map[string]any {
return item.Body
}))
json.Unmarshal(raw, &bodies)
}
- for idx, info := range posts {
+ for idx, info := range in {
if info.Publisher.AccountID != nil {
usersId = append(usersId, *info.Publisher.AccountID)
}
@@ -117,19 +89,19 @@ func ListPostV2(tx *gorm.DB, take int, offset int, order any, user *uint) ([]mod
attachmentsRid = lo.Uniq(attachmentsRid)
attachments, err := filekit.ListAttachment(gap.Nx, attachmentsRid)
if err != nil {
- return posts, fmt.Errorf("failed to load attachments: %v", err)
+ return in, 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)
+ return in, fmt.Errorf("failed to load users: %v", err)
}
// Putting information back to data
log.Info().Int("attachments", len(attachments)).Int("users", len(users)).Msg("Batch loaded metadata for listing post...")
- for idx, item := range posts {
+ for idx, item := range in {
var this []fmodels.Attachment
if len(bodies[idx].Attachments) > 0 {
this = lo.Filter(attachments, func(item fmodels.Attachment, _ int) bool {
@@ -152,7 +124,90 @@ func ListPostV2(tx *gorm.DB, take int, offset int, order any, user *uint) ([]mod
}
return acc.ID == *item.Publisher.AccountID
})
- posts[idx] = item
+ in[idx] = item
+ }
+
+ return in, nil
+}
+
+func GetPost(tx *gorm.DB, id uint, user *uint) (models.Post, error) {
+ var post models.Post
+ if err := tx.Preload("Tags").
+ Preload("Categories").
+ Preload("Publisher").
+ Preload("Poll").
+ First(&post, id).Error; err != nil {
+ return post, err
+ }
+
+ out, err := CompletePostMeta(post)
+ if err != nil {
+ return post, err
+ }
+
+ if user != nil {
+ services.AddPostView(post, *user)
+ }
+
+ return out[0], nil
+}
+
+func GetPostByAlias(tx *gorm.DB, alias, area string, user *uint) (models.Post, error) {
+ var post models.Post
+ if err := tx.Preload("Tags").
+ Preload("Categories").
+ Preload("Publisher").
+ Preload("Poll").
+ Where("alias = ?", alias).
+ Where("alias_prefix = ?", area).
+ First(&post).Error; err != nil {
+ return post, err
+ }
+
+ out, err := CompletePostMeta(post)
+ if err != nil {
+ return post, err
+ }
+
+ if user != nil {
+ services.AddPostView(post, *user)
+ }
+
+ return out[0], nil
+}
+
+func ListPost(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").
+ Preload("Poll")
+
+ // 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
+ }
+
+ // Load data eagerly
+ posts, err := CompletePostMeta(posts...)
+ if err != nil {
+ return nil, err
}
// Add post views for the user