♻️ Interactive v2 #1
@ -15,26 +15,26 @@ type Article struct {
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:ArticleID"`
|
||||
}
|
||||
|
||||
func (p Article) GetReplyTo() PostInterface {
|
||||
func (p *Article) GetReplyTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Article) GetRepostTo() PostInterface {
|
||||
func (p *Article) GetRepostTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Article) GetHashtags() []Tag {
|
||||
func (p *Article) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Article) GetCategories() []Category {
|
||||
func (p *Article) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Article) SetHashtags(tags []Tag) {
|
||||
func (p *Article) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Article) SetCategories(categories []Category) {
|
||||
func (p *Article) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
||||
|
@ -16,22 +16,22 @@ type Comment struct {
|
||||
Moment *Moment `json:"moment"`
|
||||
}
|
||||
|
||||
func (p Comment) GetReplyTo() PostInterface {
|
||||
func (p *Comment) GetReplyTo() PostInterface {
|
||||
return p.ReplyTo
|
||||
}
|
||||
|
||||
func (p Comment) GetHashtags() []Tag {
|
||||
func (p *Comment) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Comment) GetCategories() []Category {
|
||||
func (p *Comment) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Comment) SetHashtags(tags []Tag) {
|
||||
func (p *Comment) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Comment) SetCategories(categories []Category) {
|
||||
func (p *Comment) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
||||
|
@ -15,26 +15,26 @@ type Moment struct {
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:MomentID"`
|
||||
}
|
||||
|
||||
func (p Moment) GetRepostTo() PostInterface {
|
||||
func (p *Moment) GetRepostTo() PostInterface {
|
||||
return p.RepostTo
|
||||
}
|
||||
|
||||
func (p Moment) GetRealm() *Realm {
|
||||
func (p *Moment) GetRealm() *Realm {
|
||||
return p.Realm
|
||||
}
|
||||
|
||||
func (p Moment) GetHashtags() []Tag {
|
||||
func (p *Moment) GetHashtags() []Tag {
|
||||
return p.Hashtags
|
||||
}
|
||||
|
||||
func (p Moment) GetCategories() []Category {
|
||||
func (p *Moment) GetCategories() []Category {
|
||||
return p.Categories
|
||||
}
|
||||
|
||||
func (p Moment) SetHashtags(tags []Tag) {
|
||||
func (p *Moment) SetHashtags(tags []Tag) {
|
||||
p.Hashtags = tags
|
||||
}
|
||||
|
||||
func (p Moment) SetCategories(categories []Category) {
|
||||
func (p *Moment) SetCategories(categories []Category) {
|
||||
p.Categories = categories
|
||||
}
|
||||
|
@ -22,29 +22,34 @@ type PostBase struct {
|
||||
AuthorID uint `json:"author_id"`
|
||||
Author Account `json:"author"`
|
||||
|
||||
// TODO Give the reactions & replies & reposts info back
|
||||
// Dynamic Calculated Values
|
||||
ReactionList map[string]int64 `json:"reaction_list" gorm:"-"`
|
||||
}
|
||||
|
||||
func (p PostBase) GetID() uint {
|
||||
func (p *PostBase) GetID() uint {
|
||||
return p.ID
|
||||
}
|
||||
|
||||
func (p PostBase) GetReplyTo() PostInterface {
|
||||
func (p *PostBase) GetReplyTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PostBase) GetRepostTo() PostInterface {
|
||||
func (p *PostBase) GetRepostTo() PostInterface {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p PostBase) GetAuthor() Account {
|
||||
func (p *PostBase) GetAuthor() Account {
|
||||
return p.Author
|
||||
}
|
||||
|
||||
func (p PostBase) GetRealm() *Realm {
|
||||
func (p *PostBase) GetRealm() *Realm {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PostBase) SetReactionList(list map[string]int64) {
|
||||
p.ReactionList = list
|
||||
}
|
||||
|
||||
type PostInterface interface {
|
||||
GetID() uint
|
||||
GetHashtags() []Tag
|
||||
@ -56,4 +61,5 @@ type PostInterface interface {
|
||||
|
||||
SetHashtags([]Tag)
|
||||
SetCategories([]Category)
|
||||
SetReactionList(map[string]int64)
|
||||
}
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func contextArticle() *services.PostTypeContext[models.Article] {
|
||||
return &services.PostTypeContext[models.Article]{
|
||||
func contextArticle() *services.PostTypeContext[*models.Article] {
|
||||
return &services.PostTypeContext[*models.Article]{
|
||||
Tx: database.C,
|
||||
TypeName: "Article",
|
||||
CanReply: false,
|
||||
@ -31,6 +31,11 @@ func getArticle(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.ReactionList, err = mx.CountReactions(item.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
@ -102,7 +107,7 @@ func createArticle(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextArticle()
|
||||
|
||||
item := models.Article{
|
||||
item := &models.Article{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
Attachments: data.Attachments,
|
||||
@ -192,7 +197,7 @@ func reactArticle(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextArticle()
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
@ -218,7 +223,7 @@ func deleteArticle(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextArticle().FilterAuthor(user.ID)
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
@ -13,8 +13,8 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func contextComment() *services.PostTypeContext[models.Comment] {
|
||||
return &services.PostTypeContext[models.Comment]{
|
||||
func contextComment() *services.PostTypeContext[*models.Comment] {
|
||||
return &services.PostTypeContext[*models.Comment]{
|
||||
Tx: database.C,
|
||||
TypeName: "Comment",
|
||||
CanReply: false,
|
||||
@ -32,6 +32,11 @@ func getComment(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.ReactionList, err = mx.CountReactions(item.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
@ -103,7 +108,7 @@ func createComment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextComment()
|
||||
|
||||
item := models.Comment{
|
||||
item := &models.Comment{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
Attachments: data.Attachments,
|
||||
@ -199,7 +204,7 @@ func reactComment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextComment()
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
@ -225,7 +230,7 @@ func deleteComment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextComment().FilterAuthor(user.ID)
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@ -23,6 +24,7 @@ type FeedItem struct {
|
||||
RealmID *uint `json:"realm_id"`
|
||||
|
||||
Author models.Account `json:"author" gorm:"embedded"`
|
||||
ReactionList map[string]int64 `json:"reaction_list"`
|
||||
}
|
||||
|
||||
const (
|
||||
@ -56,7 +58,7 @@ func listFeed(c *fiber.Ctx) error {
|
||||
}
|
||||
}
|
||||
|
||||
var result []FeedItem
|
||||
var result []*FeedItem
|
||||
|
||||
userTable := viper.GetString("database.prefix") + "accounts"
|
||||
commentTable := viper.GetString("database.prefix") + "comments"
|
||||
@ -84,6 +86,59 @@ func listFeed(c *fiber.Ctx) error {
|
||||
offset,
|
||||
).Scan(&result)
|
||||
|
||||
if !c.QueryBool("noReact", false) {
|
||||
var reactions []struct {
|
||||
PostID uint
|
||||
Symbol string
|
||||
Count int64
|
||||
}
|
||||
|
||||
revertReaction := func(dataset string) error {
|
||||
itemMap := lo.SliceToMap(lo.FilterMap(result, func(item *FeedItem, index int) (*FeedItem, bool) {
|
||||
return item, item.ModelType == dataset
|
||||
}), func(item *FeedItem) (uint, *FeedItem) {
|
||||
return item.ID, item
|
||||
})
|
||||
|
||||
idx := lo.Map(lo.Filter(result, func(item *FeedItem, index int) bool {
|
||||
return item.ModelType == dataset
|
||||
}), func(item *FeedItem, index int) uint {
|
||||
return item.ID
|
||||
})
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select(dataset+"_id as post_id, symbol, COUNT(id) as count").
|
||||
Where(dataset+"_id IN (?)", idx).
|
||||
Group("post_id, symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
list := map[uint]map[string]int64{}
|
||||
for _, info := range reactions {
|
||||
if _, ok := list[info.PostID]; !ok {
|
||||
list[info.PostID] = make(map[string]int64)
|
||||
}
|
||||
list[info.PostID][info.Symbol] = info.Count
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.ReactionList = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := revertReaction("article"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := revertReaction("moment"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var count int64
|
||||
database.C.Raw(`SELECT COUNT(*) FROM (? UNION ALL ?) as feed`,
|
||||
database.C.Select(queryArticle).Model(&models.Article{}),
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func contextMoment() *services.PostTypeContext[models.Moment] {
|
||||
return &services.PostTypeContext[models.Moment]{
|
||||
func contextMoment() *services.PostTypeContext[*models.Moment] {
|
||||
return &services.PostTypeContext[*models.Moment]{
|
||||
Tx: database.C,
|
||||
TypeName: "Moment",
|
||||
CanReply: false,
|
||||
@ -31,6 +31,11 @@ func getMoment(c *fiber.Ctx) error {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
||||
item.ReactionList, err = mx.CountReactions(item.ID)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
|
||||
}
|
||||
|
||||
return c.JSON(item)
|
||||
}
|
||||
|
||||
@ -101,7 +106,7 @@ func createMoment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextMoment()
|
||||
|
||||
item := models.Moment{
|
||||
item := &models.Moment{
|
||||
PostBase: models.PostBase{
|
||||
Alias: data.Alias,
|
||||
Attachments: data.Attachments,
|
||||
@ -197,7 +202,7 @@ func reactMoment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextMoment()
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
@ -223,7 +228,7 @@ func deleteMoment(c *fiber.Ctx) error {
|
||||
|
||||
mx := contextMoment().FilterAuthor(user.ID)
|
||||
|
||||
item, err := mx.Get(uint(id))
|
||||
item, err := mx.Get(uint(id), true)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package services
|
||||
|
||||
import "C"
|
||||
import (
|
||||
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
|
||||
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
|
||||
@ -33,12 +34,16 @@ func (v *PostTypeContext[T]) GetTableName(plural ...bool) string {
|
||||
}
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) Preload() *PostTypeContext[T] {
|
||||
func (v *PostTypeContext[T]) Preload(noComments ...bool) *PostTypeContext[T] {
|
||||
v.Tx.Preload("Author").
|
||||
Preload("Attachments").
|
||||
Preload("Categories").
|
||||
Preload("Hashtags")
|
||||
|
||||
if len(noComments) <= 0 || !noComments[0] {
|
||||
v.Tx = v.Tx.Preload("Comments")
|
||||
}
|
||||
|
||||
if v.CanReply {
|
||||
v.Tx.Preload("ReplyTo")
|
||||
}
|
||||
@ -98,18 +103,18 @@ func (v *PostTypeContext[T]) SortCreatedAt(order string) *PostTypeContext[T] {
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) GetViaAlias(alias string) (T, error) {
|
||||
func (v *PostTypeContext[T]) GetViaAlias(alias string, noComments ...bool) (T, error) {
|
||||
var item T
|
||||
if err := v.Preload().Tx.Where("alias = ?", alias).First(&item).Error; err != nil {
|
||||
if err := v.Preload(noComments...).Tx.Where("alias = ?", alias).First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) Get(id uint) (T, error) {
|
||||
func (v *PostTypeContext[T]) Get(id uint, noComments ...bool) (T, error) {
|
||||
var item T
|
||||
if err := v.Preload().Tx.Where("id = ?", id).First(&item).Error; err != nil {
|
||||
if err := v.Preload(noComments...).Tx.Where("id = ?", id).First(&item).Error; err != nil {
|
||||
return item, err
|
||||
}
|
||||
|
||||
@ -126,6 +131,28 @@ func (v *PostTypeContext[T]) Count() (int64, error) {
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) CountReactions(id uint) (map[string]int64, error) {
|
||||
var reactions []struct {
|
||||
Symbol string
|
||||
Count int64
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select("symbol, COUNT(id) as count").
|
||||
Where(strings.ToLower(v.TypeName)+"_id = ?", id).
|
||||
Group("symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return map[string]int64{}, err
|
||||
}
|
||||
|
||||
return lo.SliceToMap(reactions, func(item struct {
|
||||
Symbol string
|
||||
Count int64
|
||||
}) (string, int64) {
|
||||
return item.Symbol, item.Count
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, error) {
|
||||
if take > 20 {
|
||||
take = 20
|
||||
@ -136,6 +163,44 @@ func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, e
|
||||
return items, err
|
||||
}
|
||||
|
||||
idx := lo.Map(items, func(item T, index int) uint {
|
||||
return item.GetID()
|
||||
})
|
||||
|
||||
if len(noReact) <= 0 || !noReact[0] {
|
||||
var reactions []struct {
|
||||
PostID uint
|
||||
Symbol string
|
||||
Count int64
|
||||
}
|
||||
|
||||
if err := database.C.Model(&models.Reaction{}).
|
||||
Select(strings.ToLower(v.TypeName)+"_id as post_id, symbol, COUNT(id) as count").
|
||||
Where(strings.ToLower(v.TypeName)+"_id IN (?)", idx).
|
||||
Group("post_id, symbol").
|
||||
Scan(&reactions).Error; err != nil {
|
||||
return items, err
|
||||
}
|
||||
|
||||
itemMap := lo.SliceToMap(items, func(item T) (uint, T) {
|
||||
return item.GetID(), item
|
||||
})
|
||||
|
||||
list := map[uint]map[string]int64{}
|
||||
for _, info := range reactions {
|
||||
if _, ok := list[info.PostID]; !ok {
|
||||
list[info.PostID] = make(map[string]int64)
|
||||
}
|
||||
list[info.PostID][info.Symbol] = info.Count
|
||||
}
|
||||
|
||||
for k, v := range list {
|
||||
if post, ok := itemMap[k]; ok {
|
||||
post.SetReactionList(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
open-on-hover
|
||||
open-on-click
|
||||
:open-delay="0"
|
||||
:close-delay="1850"
|
||||
:close-delay="0"
|
||||
location="top"
|
||||
transition="scroll-y-reverse-transition"
|
||||
>
|
||||
|
Loading…
Reference in New Issue
Block a user