✨ Recommendation API
This commit is contained in:
parent
8429d72ad1
commit
2cf24c4724
@ -16,4 +16,7 @@ type Account struct {
|
|||||||
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
|
Posts []Post `json:"posts" gorm:"foreignKey:AuthorID"`
|
||||||
Reactions []Reaction `json:"reactions"`
|
Reactions []Reaction `json:"reactions"`
|
||||||
ExternalID uint `json:"external_id"`
|
ExternalID uint `json:"external_id"`
|
||||||
|
|
||||||
|
TotalUpvote int `json:"total_upvote"`
|
||||||
|
TotalDownvote int `json:"total_downvote"`
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@ func MapAPIs(app *fiber.App, baseURL string) {
|
|||||||
api.Get("/users/me", getUserinfo)
|
api.Get("/users/me", getUserinfo)
|
||||||
api.Get("/users/:accountId", getOthersInfo)
|
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 := api.Group("/stories").Name("Story API")
|
||||||
{
|
{
|
||||||
stories.Post("/", createStory)
|
stories.Post("/", createStory)
|
||||||
|
@ -66,7 +66,7 @@ func listPost(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, err.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 {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func listDraftPost(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, err.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 {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
75
pkg/internal/server/api/recommendation_api.go
Normal file
75
pkg/internal/server/api/recommendation_api.go
Normal 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,
|
||||||
|
})
|
||||||
|
}
|
@ -40,7 +40,7 @@ func listPostReplies(c *fiber.Ctx) error {
|
|||||||
return fiber.NewError(fiber.StatusInternalServerError, err.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 {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,23 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
|
"git.solsynth.dev/hydrogen/dealer/pkg/hyper"
|
||||||
"git.solsynth.dev/hydrogen/dealer/pkg/proto"
|
"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/gap"
|
||||||
"git.solsynth.dev/hydrogen/interactive/pkg/internal/models"
|
"git.solsynth.dev/hydrogen/interactive/pkg/internal/models"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"time"
|
"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 {
|
func NotifyPosterAccount(user models.Account, title, body string, subtitle *string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -57,14 +57,8 @@ func FilterPostDraft(tx *gorm.DB) *gorm.DB {
|
|||||||
return tx.Where("is_draft = ? OR is_draft IS NULL", false)
|
return tx.Where("is_draft = ? OR is_draft IS NULL", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPost(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Post, error) {
|
func PreloadGeneral(tx *gorm.DB) *gorm.DB {
|
||||||
if len(ignoreLimitation) == 0 || !ignoreLimitation[0] {
|
return tx.
|
||||||
tx = FilterPostWithPublishedAt(tx, time.Now())
|
|
||||||
}
|
|
||||||
|
|
||||||
var item models.Post
|
|
||||||
if err := tx.
|
|
||||||
Where("id = ?", id).
|
|
||||||
Preload("Tags").
|
Preload("Tags").
|
||||||
Preload("Categories").
|
Preload("Categories").
|
||||||
Preload("Realm").
|
Preload("Realm").
|
||||||
@ -76,7 +70,17 @@ func GetPost(tx *gorm.DB, id uint, ignoreLimitation ...bool) (models.Post, error
|
|||||||
Preload("RepostTo").
|
Preload("RepostTo").
|
||||||
Preload("RepostTo.Author").
|
Preload("RepostTo.Author").
|
||||||
Preload("RepostTo.Tags").
|
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 {
|
First(&item).Error; err != nil {
|
||||||
return item, err
|
return item, err
|
||||||
}
|
}
|
||||||
@ -115,27 +119,15 @@ func CountPostReactions(id uint) int64 {
|
|||||||
return count
|
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 {
|
if take > 100 {
|
||||||
take = 100
|
take = 100
|
||||||
}
|
}
|
||||||
|
|
||||||
var items []*models.Post
|
var items []*models.Post
|
||||||
if err := tx.
|
if err := PreloadGeneral(tx).
|
||||||
Limit(take).Offset(offset).
|
Limit(take).Offset(offset).
|
||||||
Order("published_at DESC").
|
Order(order).
|
||||||
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").
|
|
||||||
Find(&items).Error; err != nil {
|
Find(&items).Error; err != nil {
|
||||||
return items, err
|
return items, err
|
||||||
}
|
}
|
||||||
@ -278,13 +270,16 @@ func DeletePost(item models.Post) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReactPost(user models.Account, reaction models.Reaction) (bool, models.Reaction, error) {
|
func ReactPost(user models.Account, reaction models.Reaction) (bool, models.Reaction, error) {
|
||||||
if err := database.C.Where(reaction).First(&reaction).Error; err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
var op models.Post
|
var op models.Post
|
||||||
if err := database.C.
|
if err := database.C.
|
||||||
Where("id = ?", reaction.PostID).
|
Where("id = ?", reaction.PostID).
|
||||||
Preload("Author").
|
Preload("Author").
|
||||||
First(&op).Error; err == nil {
|
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) {
|
||||||
if op.Author.ID != user.ID {
|
if op.Author.ID != user.ID {
|
||||||
err = NotifyPosterAccount(
|
err = NotifyPosterAccount(
|
||||||
op.Author,
|
op.Author,
|
||||||
@ -296,13 +291,22 @@ func ReactPost(user models.Account, reaction models.Reaction) (bool, models.Reac
|
|||||||
log.Error().Err(err).Msg("An error occurred when notifying user...")
|
log.Error().Err(err).Msg("An error occurred when notifying user...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = database.C.Save(&reaction).Error
|
||||||
|
if err == nil {
|
||||||
|
_ = ModifyPosterVoteCount(op.Author, reaction.Attitude == models.AttitudePositive, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, reaction, database.C.Save(&reaction).Error
|
return true, reaction, err
|
||||||
} else {
|
} else {
|
||||||
return true, reaction, err
|
return true, reaction, err
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user