Recommendation API

This commit is contained in:
LittleSheep 2024-07-23 16:12:19 +08:00
parent 8429d72ad1
commit 2cf24c4724
7 changed files with 143 additions and 44 deletions

View File

@ -16,4 +16,7 @@ type Account struct {
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
Reactions []Reaction `json:"reactions"`
ExternalID uint `json:"external_id"`
TotalUpvote int `json:"total_upvote"`
TotalDownvote int `json:"total_downvote"`
}

View File

@ -10,6 +10,12 @@ func MapAPIs(app *fiber.App, baseURL string) {
api.Get("/users/me", getUserinfo)
api.Get("/users/:accountId", getOthersInfo)
recommendations := api.Group("/recommendations").Name("Recommendations API")
{
recommendations.Get("/", listRecommendationDefault)
recommendations.Get("/shuffle", listRecommendationShuffle)
}
stories := api.Group("/stories").Name("Story API")
{
stories.Post("/", createStory)

View File

@ -66,7 +66,7 @@ func listPost(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
items, err := services.ListPost(tx, take, offset)
items, err := services.ListPost(tx, take, offset, "published_at DESC")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
@ -93,7 +93,7 @@ func listDraftPost(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
items, err := services.ListPost(tx, take, offset, true)
items, err := services.ListPost(tx, take, offset, "created_at DESC", true)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}

View File

@ -0,0 +1,75 @@
package api
import (
"fmt"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/database"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/services"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
func listRecommendationDefault(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
realmId := c.QueryInt("realmId", 0)
maxDownVote := c.QueryInt("maxDownVote", 3)
tx := database.C.Joins("Author").Where("accounts.total_downvote <= ?", maxDownVote)
tx = services.FilterPostDraft(tx)
if realmId > 0 {
if realm, err := services.GetRealmWithExtID(uint(realmId)); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err))
} else {
tx = services.FilterPostWithRealm(tx, realm.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")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": items,
})
}
func listRecommendationShuffle(c *fiber.Ctx) error {
take := c.QueryInt("take", 0)
offset := c.QueryInt("offset", 0)
realmId := c.QueryInt("realmId", 0)
maxDownVote := c.QueryInt("maxDownVote", 3)
tx := database.C.Joins("Author").Where("accounts.total_downvote <= ?", maxDownVote)
tx = services.FilterPostDraft(tx)
if realmId > 0 {
if realm, err := services.GetRealmWithExtID(uint(realmId)); err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("realm was not found: %v", err))
} else {
tx = services.FilterPostWithRealm(tx, realm.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, gorm.Expr("RAND()"))
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(fiber.Map{
"count": count,
"data": items,
})
}

View File

@ -40,7 +40,7 @@ func listPostReplies(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
items, err := services.ListPost(tx, take, offset)
items, err := services.ListPost(tx, take, offset, "published_at DESC")
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}

View File

@ -4,12 +4,23 @@ import (
"context"
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/database"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/gap"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/models"
"github.com/rs/zerolog/log"
"time"
)
func ModifyPosterVoteCount(user models.Account, isUpvote bool, delta int) error {
if isUpvote {
user.TotalUpvote += delta
} else {
user.TotalDownvote += delta
}
return database.C.Save(&user).Error
}
func NotifyPosterAccount(user models.Account, title, body string, subtitle *string) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

View File

@ -57,14 +57,8 @@ func FilterPostDraft(tx *gorm.DB) *gorm.DB {
return tx.Where("is_draft = ? OR is_draft IS NULL", false)
}
func GetPost(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Post, error) {
if len(ignoreLimitation) == 0 || !ignoreLimitation[0] {
tx = FilterPostWithPublishedAt(tx, time.Now())
}
var item models.Post
if err := tx.
Where("id = ?", id).
func PreloadGeneral(tx *gorm.DB) *gorm.DB {
return tx.
Preload("Tags").
Preload("Categories").
Preload("Realm").
@ -76,7 +70,17 @@ func GetPost(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Post, error
Preload("RepostTo").
Preload("RepostTo.Author").
Preload("RepostTo.Tags").
Preload("RepostTo.Categories").
Preload("RepostTo.Categories")
}
func GetPost(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Post, error) {
if len(ignoreLimitation) == 0 || !ignoreLimitation[0] {
tx = FilterPostWithPublishedAt(tx, time.Now())
}
var item models.Post
if err := PreloadGeneral(tx).
Where("id = ?", id).
First(&item).Error; err != nil {
return item, err
}
@ -115,27 +119,15 @@ func CountPostReactions(id uint) int64 {
return count
}
func ListPost(tx *gorm.DB, take int, offset int, noReact ...bool) ([]*models.Post, error) {
func ListPost(tx *gorm.DB, take int, offset int, order any, noReact ...bool) ([]*models.Post, error) {
if take > 100 {
take = 100
}
var items []*models.Post
if err := tx.
if err := PreloadGeneral(tx).
Limit(take).Offset(offset).
Order("published_at DESC").
Preload("Tags").
Preload("Categories").
Preload("Realm").
Preload("Author").
Preload("ReplyTo").
Preload("ReplyTo.Author").
Preload("ReplyTo.Tags").
Preload("ReplyTo.Categories").
Preload("RepostTo").
Preload("RepostTo.Author").
Preload("RepostTo.Tags").
Preload("RepostTo.Categories").
Order(order).
Find(&items).Error; err != nil {
return items, err
}
@ -278,31 +270,43 @@ func DeletePost(item models.Post) error {
}
func ReactPost(user models.Account, reaction models.Reaction) (bool, models.Reaction, error) {
var op models.Post
if err := database.C.
Where("id = ?", reaction.PostID).
Preload("Author").
First(&op).Error; err != nil {
return true, reaction, err
}
if err := database.C.Where(reaction).First(&reaction).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
var op models.Post
if err := database.C.
Where("id = ?", reaction.PostID).
Preload("Author").
First(&op).Error; err == nil {
if op.Author.ID != user.ID {
err = NotifyPosterAccount(
op.Author,
"Post got reacted",
fmt.Sprintf("%s (%s) reacted your post a %s.", user.Nick, user.Name, reaction.Symbol),
lo.ToPtr(fmt.Sprintf("%s reacted you", user.Nick)),
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")
}
if op.Author.ID != user.ID {
err = NotifyPosterAccount(
op.Author,
"Post got reacted",
fmt.Sprintf("%s (%s) reacted your post a %s.", user.Nick, user.Name, reaction.Symbol),
lo.ToPtr(fmt.Sprintf("%s reacted you", user.Nick)),
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")
}
}
return true, reaction, database.C.Save(&reaction).Error
err = database.C.Save(&reaction).Error
if err == nil {
_ = ModifyPosterVoteCount(op.Author, reaction.Attitude == models.AttitudePositive, 1)
}
return true, reaction, err
} else {
return true, reaction, err
}
} else {
return false, reaction, database.C.Delete(&reaction).Error
err = database.C.Delete(&reaction).Error
if err == nil {
_ = ModifyPosterVoteCount(op.Author, reaction.Attitude == models.AttitudePositive, -1)
}
return false, reaction, err
}
}