✨ Count post views
This commit is contained in:
		@@ -15,6 +15,7 @@ var AutoMaintainRange = []any{
 | 
			
		||||
	&models.Poll{},
 | 
			
		||||
	&models.PollAnswer{},
 | 
			
		||||
	&models.PostFlag{},
 | 
			
		||||
	&models.PostView{},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RunMigration(source *gorm.DB) error {
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								pkg/internal/services/post_views.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								pkg/internal/services/post_views.go
									
									
									
									
									
										Normal file
									
								
							@@ -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),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user