✨ 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"`
|
||||
Reactions []Reaction `json:"reactions"`
|
||||
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/: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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
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())
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user