✨ Reaction info
This commit is contained in:
		| @@ -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" | ||||
| ) | ||||
|  | ||||
| @@ -22,7 +23,8 @@ type FeedItem struct { | ||||
| 	AuthorID      uint   `json:"author_id"` | ||||
| 	RealmID       *uint  `json:"realm_id"` | ||||
|  | ||||
| 	Author models.Account `json:"author" gorm:"embedded"` | ||||
| 	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" | ||||
|   > | ||||
|   | ||||
		Reference in New Issue
	
	Block a user