♻️ Globally apply the V2 api
This commit is contained in:
		
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| # 默认忽略的文件 | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # 基于编辑器的 HTTP 客户端请求 | ||||
| /httpRequests/ | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
							
								
								
									
										9
									
								
								.idea/Interactive.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								.idea/Interactive.iml
									
									
									
										generated
									
									
									
								
							| @@ -1,9 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <module type="WEB_MODULE" version="4"> | ||||
|   <component name="Go" enabled="true" /> | ||||
|   <component name="NewModuleRootManager"> | ||||
|     <content url="file://$MODULE_DIR$" /> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|     <orderEntry type="sourceFolder" forTests="false" /> | ||||
|   </component> | ||||
| </module> | ||||
							
								
								
									
										12
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								.idea/dataSources.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,12 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="DataSourceManagerImpl" format="xml" multifile-model="true"> | ||||
|     <data-source source="LOCAL" name="hy_interactive@localhost" uuid="a2f70c83-03f8-4240-bb8b-ac697502cfe2"> | ||||
|       <driver-ref>postgresql</driver-ref> | ||||
|       <synchronize>true</synchronize> | ||||
|       <jdbc-driver>org.postgresql.Driver</jdbc-driver> | ||||
|       <jdbc-url>jdbc:postgresql://localhost:5432/hy_interactive</jdbc-url> | ||||
|       <working-dir>$ProjectFileDir$</working-dir> | ||||
|     </data-source> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										6
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/inspectionProfiles/Project_Default.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +0,0 @@ | ||||
| <component name="InspectionProjectProfileManager"> | ||||
|   <profile version="1.0"> | ||||
|     <option name="myName" value="Project Default" /> | ||||
|     <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" /> | ||||
|   </profile> | ||||
| </component> | ||||
							
								
								
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								.idea/modules.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="ProjectModuleManager"> | ||||
|     <modules> | ||||
|       <module fileurl="file://$PROJECT_DIR$/.idea/Interactive.iml" filepath="$PROJECT_DIR$/.idea/Interactive.iml" /> | ||||
|     </modules> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +0,0 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -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 { | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -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()) | ||||
| 	} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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()) | ||||
| 	} | ||||
|   | ||||
| @@ -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()) | ||||
| 	} | ||||
|   | ||||
| @@ -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, | ||||
| 		} | ||||
| 	}) | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user