From 269e79ca58d8f3d06c97bf562b626640fc14de01 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Mon, 17 Feb 2025 17:21:15 +0800 Subject: [PATCH] :sparkles: Count post views --- pkg/internal/database/migrator.go | 1 + pkg/internal/http/api/activitypub_api.go | 5 ++- pkg/internal/http/api/posts_api.go | 26 ++++++++--- pkg/internal/http/api/publishers_api.go | 7 ++- pkg/internal/http/api/recommendation_api.go | 21 +++++++-- pkg/internal/http/api/replies_api.go | 16 ++++++- pkg/internal/http/api/what_new_api.dart.go | 7 ++- pkg/internal/models/posts.go | 13 +++++- pkg/internal/services/post_views.go | 49 +++++++++++++++++++++ pkg/internal/services/posts.go | 8 +++- pkg/main.go | 8 ++-- 11 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 pkg/internal/services/post_views.go diff --git a/pkg/internal/database/migrator.go b/pkg/internal/database/migrator.go index 3bb6b6f..1b795c6 100644 --- a/pkg/internal/database/migrator.go +++ b/pkg/internal/database/migrator.go @@ -15,6 +15,7 @@ var AutoMaintainRange = []any{ &models.Poll{}, &models.PollAnswer{}, &models.PostFlag{}, + &models.PostView{}, } func RunMigration(source *gorm.DB) error { diff --git a/pkg/internal/http/api/activitypub_api.go b/pkg/internal/http/api/activitypub_api.go index 9a829db..b810706 100644 --- a/pkg/internal/http/api/activitypub_api.go +++ b/pkg/internal/http/api/activitypub_api.go @@ -2,13 +2,14 @@ package api import ( "fmt" + "time" + "git.solsynth.dev/hypernet/interactive/pkg/internal/database" "git.solsynth.dev/hypernet/interactive/pkg/internal/models" "git.solsynth.dev/hypernet/interactive/pkg/internal/services" vocab "github.com/go-ap/activitypub" "github.com/gofiber/fiber/v2" "github.com/samber/lo" - "time" ) func apGetPublisher(c *fiber.Ctx) error { @@ -42,7 +43,7 @@ func apGetPost(c *fiber.Ctx) error { return err } - items, err := services.ListPost(tx, take, offset, "published_at DESC") + items, err := services.ListPost(tx, take, offset, "published_at DESC", nil) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/internal/http/api/posts_api.go b/pkg/internal/http/api/posts_api.go index 84b99a3..84c5fe7 100644 --- a/pkg/internal/http/api/posts_api.go +++ b/pkg/internal/http/api/posts_api.go @@ -2,13 +2,14 @@ package api import ( "fmt" + "strconv" + "strings" + "git.solsynth.dev/hypernet/nexus/pkg/nex/cruda" "git.solsynth.dev/hypernet/nexus/pkg/nex/sec" "git.solsynth.dev/hypernet/passport/pkg/authkit" authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models" "gorm.io/gorm" - "strconv" - "strings" "git.solsynth.dev/hypernet/interactive/pkg/internal/database" "git.solsynth.dev/hypernet/interactive/pkg/internal/gap" @@ -114,13 +115,18 @@ func searchPost(c *fiber.Ctx) error { 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 := services.ListPost(tx, take, offset, "published_at DESC") + items, err := services.ListPost(tx, take, offset, "published_at DESC", userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -150,13 +156,18 @@ func listPost(c *fiber.Ctx) error { 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 := services.ListPost(tx, take, offset, "published_at DESC") + items, err := services.ListPost(tx, take, offset, "published_at DESC", userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -222,12 +233,17 @@ func listDraftPost(c *fiber.Ctx) error { tx := services.FilterPostWithAuthorDraft(database.C, user.ID) + var userId *uint + if user, authenticated := c.Locals("user").(authm.Account); authenticated { + userId = &user.ID + } + count, err := services.CountPost(tx) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } - items, err := services.ListPost(tx, take, offset, "created_at DESC", true) + items, err := services.ListPost(tx, take, offset, "created_at DESC", userId, true) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/internal/http/api/publishers_api.go b/pkg/internal/http/api/publishers_api.go index 523f865..6765021 100644 --- a/pkg/internal/http/api/publishers_api.go +++ b/pkg/internal/http/api/publishers_api.go @@ -28,7 +28,12 @@ func listPinnedPost(c *fiber.Ctx) error { tx = tx.Where("publisher_id = ?", user.ID) tx = tx.Where("pinned_at IS NOT NULL") - items, err := services.ListPost(tx, 100, 0, "published_at DESC") + var userId *uint + if user, authenticated := c.Locals("user").(authm.Account); authenticated { + userId = &user.ID + } + + items, err := services.ListPost(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 0022812..b018796 100644 --- a/pkg/internal/http/api/recommendation_api.go +++ b/pkg/internal/http/api/recommendation_api.go @@ -25,8 +25,13 @@ func listRecommendation(c *fiber.Ctx) error { return item.ID }) + var userId *uint + if user, authenticated := c.Locals("user").(authm.Account); authenticated { + userId = &user.ID + } + tx := database.C.Where("id IN ?", postIdx) - newPosts, err := services.ListPost(tx, featuredMax, 0, "id ASC") + newPosts, err := services.ListPost(tx, featuredMax, 0, "id ASC", userId) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } @@ -65,6 +70,11 @@ func listRecommendationFriends(c *fiber.Ctx) error { tx = tx.Where("publisher_id IN ?", friendList) + 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 { @@ -76,7 +86,7 @@ func listRecommendationFriends(c *fiber.Ctx) error { order = "published_at DESC, (COALESCE(total_upvote, 0) - COALESCE(total_downvote, 0)) DESC" } - items, err := services.ListPost(tx, take, offset, order) + items, err := services.ListPost(tx, take, offset, order, userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -106,13 +116,18 @@ func listRecommendationShuffle(c *fiber.Ctx) error { 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 := services.ListPost(tx, take, offset, "RANDOM()") + items, err := services.ListPost(tx, take, offset, "RANDOM()", userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/internal/http/api/replies_api.go b/pkg/internal/http/api/replies_api.go index c3d812a..4b58cec 100644 --- a/pkg/internal/http/api/replies_api.go +++ b/pkg/internal/http/api/replies_api.go @@ -2,9 +2,11 @@ package api import ( "fmt" + "git.solsynth.dev/hypernet/interactive/pkg/internal/database" "git.solsynth.dev/hypernet/interactive/pkg/internal/models" "git.solsynth.dev/hypernet/interactive/pkg/internal/services" + authm "git.solsynth.dev/hypernet/passport/pkg/authkit/models" "github.com/gofiber/fiber/v2" ) @@ -35,12 +37,17 @@ func listPostReplies(c *fiber.Ctx) error { tx = services.FilterPostWithTag(tx, c.Query("tag")) } + var userId *uint + if user, authenticated := c.Locals("user").(authm.Account); authenticated { + userId = &user.ID + } + count, err := services.CountPost(tx) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } - items, err := services.ListPost(tx, take, offset, "published_at DESC") + items, err := services.ListPost(tx, take, offset, "published_at DESC", userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } @@ -55,6 +62,11 @@ func listPostFeaturedReply(c *fiber.Ctx) error { take := c.QueryInt("take", 0) take = max(1, min(take, 3)) + var userId *uint + if user, authenticated := c.Locals("user").(authm.Account); authenticated { + userId = &user.ID + } + tx := database.C var post models.Post if err := database.C.Where("id = ?", c.Params("postId")).First(&post).Error; err != nil { @@ -78,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") + items, err := services.ListPost(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 58825a2..25b950b 100644 --- a/pkg/internal/http/api/what_new_api.dart.go +++ b/pkg/internal/http/api/what_new_api.dart.go @@ -24,6 +24,11 @@ func getWhatsNew(c *fiber.Ctx) error { tx = tx.Where("id > ?", pivot) + 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 { @@ -35,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) + items, err := services.ListPost(tx, 10, 0, order, userId) if err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } diff --git a/pkg/internal/models/posts.go b/pkg/internal/models/posts.go index 7719e54..090919c 100644 --- a/pkg/internal/models/posts.go +++ b/pkg/internal/models/posts.go @@ -56,8 +56,10 @@ type Post struct { PublishedAt *time.Time `json:"published_at"` PublishedUntil *time.Time `json:"published_until"` - TotalUpvote int `json:"total_upvote"` - TotalDownvote int `json:"total_downvote"` + TotalUpvote int `json:"total_upvote"` + TotalDownvote int `json:"total_downvote"` + TotalViews int64 `json:"total_views"` + TotalAggressiveViews int64 `json:"total_aggressive_views"` PollID *uint `json:"poll_id"` Poll *Poll `json:"poll"` @@ -111,3 +113,10 @@ type PostInsight struct { Post Post `json:"post"` PostID uint `json:"post_id"` } + +type PostView struct { + AccountID uint `json:"account_id" gorm:"primaryKey"` + PostID uint `json:"post_id" gorm:"primaryKey"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/pkg/internal/services/post_views.go b/pkg/internal/services/post_views.go new file mode 100644 index 0000000..b4477d7 --- /dev/null +++ b/pkg/internal/services/post_views.go @@ -0,0 +1,49 @@ +package services + +import ( + "git.solsynth.dev/hypernet/interactive/pkg/internal/database" + "git.solsynth.dev/hypernet/interactive/pkg/internal/models" + "gorm.io/gorm" +) + +var postViewQueue []models.PostView + +func AddPostView(post models.Post, account uint) { + postViewQueue = append(postViewQueue, models.PostView{ + AccountID: account, + PostID: post.ID, + }) +} + +func AddPostViews(posts []models.Post, account uint) { + for _, post := range posts { + postViewQueue = append(postViewQueue, models.PostView{ + AccountID: account, + PostID: post.ID, + }) + } +} + +func FlushPostViews() { + if len(postViewQueue) == 0 { + return + } + workingQueue := make([]models.PostView, len(postViewQueue)) + copy(workingQueue, postViewQueue) + clear(postViewQueue) + updateRequiredPost := make(map[uint]bool) + for _, item := range workingQueue { + updateRequiredPost[item.PostID] = true + } + _ = database.C.CreateInBatches(workingQueue, 1000).Error + for k := range updateRequiredPost { + var count int64 + if err := database.C.Model(&models.PostView{}).Where("post_id = ?", k).Count(&count).Error; err != nil { + continue + } + database.C.Model(&models.Post{}).Where("id = ?", k).Updates(map[string]any{ + "total_views": count, + "total_aggressive_views": gorm.Expr("total_aggressive_views + ?", count), + }) + } +} diff --git a/pkg/internal/services/posts.go b/pkg/internal/services/posts.go index 588835c..013e452 100644 --- a/pkg/internal/services/posts.go +++ b/pkg/internal/services/posts.go @@ -262,7 +262,7 @@ func CountPostReactions(id uint) int64 { return count } -func ListPost(tx *gorm.DB, take int, offset int, order any, noReact ...bool) ([]*models.Post, error) { +func ListPost(tx *gorm.DB, take int, offset int, order any, user *uint, noReact ...bool) ([]*models.Post, error) { if take > 100 { take = 100 } @@ -332,6 +332,12 @@ func ListPost(tx *gorm.DB, take int, offset int, order any, noReact ...bool) ([] } } + if user != nil { + AddPostViews(lo.Map(items, func(item *models.Post, index int) models.Post { + return *item + }), *user) + } + return items, nil } diff --git a/pkg/main.go b/pkg/main.go index e0a94fe..c38228b 100644 --- a/pkg/main.go +++ b/pkg/main.go @@ -2,14 +2,15 @@ package main import ( "fmt" + "os" + "os/signal" + "syscall" + pkg "git.solsynth.dev/hypernet/interactive/pkg/internal" "git.solsynth.dev/hypernet/interactive/pkg/internal/cache" "git.solsynth.dev/hypernet/interactive/pkg/internal/gap" "git.solsynth.dev/hypernet/nexus/pkg/nex/sec" "github.com/fatih/color" - "os" - "os/signal" - "syscall" "git.solsynth.dev/hypernet/interactive/pkg/internal/database" "git.solsynth.dev/hypernet/interactive/pkg/internal/grpc" @@ -67,6 +68,7 @@ func main() { // Configure timed tasks quartz := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(&log.Logger))) quartz.AddFunc("@every 60m", services.DoAutoDatabaseCleanup) + quartz.AddFunc("@every 1m", services.FlushPostViews) quartz.Start() // Initialize cache