Interactive/pkg/services/posts.go

365 lines
9.3 KiB
Go
Raw Normal View History

2024-02-02 15:42:42 +00:00
package services
import (
"errors"
"fmt"
"time"
"code.smartsheep.studio/hydrogen/identity/pkg/grpc/proto"
2024-02-03 16:24:04 +00:00
"code.smartsheep.studio/hydrogen/interactive/pkg/database"
"code.smartsheep.studio/hydrogen/interactive/pkg/models"
2024-03-02 17:23:11 +00:00
"github.com/rs/zerolog/log"
2024-02-03 07:20:32 +00:00
"github.com/samber/lo"
"github.com/spf13/viper"
2024-02-02 16:50:23 +00:00
"gorm.io/gorm"
2024-03-02 17:23:11 +00:00
)
type PostTypeContext struct {
2024-03-02 17:23:11 +00:00
Tx *gorm.DB
TableName string
ColumnName string
CanReply bool
CanRepost bool
2024-02-05 07:51:31 +00:00
}
func (v *PostTypeContext) FilterWithCategory(alias string) *PostTypeContext {
name := v.ColumnName
v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", name, v.TableName, name, name)).
Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", name, name, name)).
Where(name+"_categories.alias = ?", alias)
2024-03-02 17:23:11 +00:00
return v
}
func (v *PostTypeContext) FilterWithTag(alias string) *PostTypeContext {
name := v.ColumnName
v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", name, v.TableName, name, name)).
Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", name, name, name)).
Where(name+"_tags.alias = ?", alias)
2024-03-02 17:23:11 +00:00
return v
}
func (v *PostTypeContext) FilterPublishedAt(date time.Time) *PostTypeContext {
2024-03-02 17:23:11 +00:00
v.Tx.Where("published_at <= ? AND published_at IS NULL", date)
return v
}
func (v *PostTypeContext) FilterRealm(id uint) *PostTypeContext {
2024-03-02 17:23:11 +00:00
if id > 0 {
v.Tx = v.Tx.Where("realm_id = ?", id)
} else {
v.Tx = v.Tx.Where("realm_id IS NULL")
}
2024-03-02 17:23:11 +00:00
return v
}
func (v *PostTypeContext) FilterAuthor(id uint) *PostTypeContext {
2024-03-02 17:23:11 +00:00
v.Tx = v.Tx.Where("author_id = ?", id)
return v
}
func (v *PostTypeContext) FilterReply(condition bool) *PostTypeContext {
2024-03-02 17:23:11 +00:00
if condition {
v.Tx = v.Tx.Where("reply_id IS NOT NULL")
} else {
v.Tx = v.Tx.Where("reply_id IS NULL")
}
2024-03-02 17:23:11 +00:00
return v
}
func (v *PostTypeContext) SortCreatedAt(order string) *PostTypeContext {
2024-03-02 17:23:11 +00:00
v.Tx.Order(fmt.Sprintf("created_at %s", order))
return v
}
2024-03-06 14:35:10 +00:00
func (v *PostTypeContext) GetViaAlias(alias string) (models.Feed, error) {
var item models.Feed
2024-03-06 14:35:10 +00:00
table := viper.GetString("database.prefix") + v.TableName
2024-03-11 15:47:19 +00:00
userTable := viper.GetString("database.prefix") + "accounts"
2024-03-06 14:35:10 +00:00
if err := v.Tx.
Table(table).
2024-03-07 14:41:00 +00:00
Select("*, ? as model_type", v.ColumnName).
2024-03-11 15:47:19 +00:00
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
2024-03-06 14:35:10 +00:00
Where("alias = ?", alias).
First(&item).Error; err != nil {
2024-03-03 13:24:08 +00:00
return item, err
}
2024-03-10 15:35:38 +00:00
var attachments []models.Attachment
if err := database.C.
Model(&models.Attachment{}).
Where(v.ColumnName+"_id = ?", item.ID).
Scan(&attachments).Error; err != nil {
return item, err
} else {
item.Attachments = attachments
}
2024-03-03 13:24:08 +00:00
return item, nil
}
func (v *PostTypeContext) Get(id uint, noComments ...bool) (models.Feed, error) {
var item models.Feed
2024-03-06 14:35:10 +00:00
table := viper.GetString("database.prefix") + v.TableName
2024-03-11 15:47:19 +00:00
userTable := viper.GetString("database.prefix") + "accounts"
if err := v.Tx.
2024-03-06 14:35:10 +00:00
Table(table).
Select("*, ? as model_type", v.ColumnName).
2024-03-11 15:47:19 +00:00
Joins(fmt.Sprintf("INNER JOIN %s AS author ON author_id = author.id", userTable)).
Where("id = ?", id).First(&item).Error; err != nil {
2024-03-02 17:23:11 +00:00
return item, err
}
2024-03-10 15:35:38 +00:00
var attachments []models.Attachment
if err := database.C.
Model(&models.Attachment{}).
Where(v.ColumnName+"_id = ?", id).
Scan(&attachments).Error; err != nil {
return item, err
} else {
item.Attachments = attachments
}
2024-03-02 17:23:11 +00:00
return item, nil
}
func (v *PostTypeContext) Count() (int64, error) {
2024-03-02 17:23:11 +00:00
var count int64
table := viper.GetString("database.prefix") + v.TableName
2024-03-02 17:23:11 +00:00
if err := v.Tx.Table(table).Count(&count).Error; err != nil {
return count, err
}
return count, nil
}
func (v *PostTypeContext) CountReactions(id uint) (map[string]int64, error) {
2024-03-03 14:57:17 +00:00
var reactions []struct {
Symbol string
Count int64
}
if err := database.C.Model(&models.Reaction{}).
Select("symbol, COUNT(id) as count").
Where(v.ColumnName+"_id = ?", id).
2024-03-03 14:57:17 +00:00
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) {
2024-03-03 14:57:17 +00:00
return item.Symbol, item.Count
}), nil
}
func (v *PostTypeContext) List(take int, offset int, noReact ...bool) ([]*models.Feed, error) {
2024-02-12 04:32:37 +00:00
if take > 20 {
take = 20
}
var items []*models.Feed
2024-03-06 14:35:10 +00:00
table := viper.GetString("database.prefix") + v.TableName
if err := v.Tx.
2024-03-06 14:35:10 +00:00
Table(table).
Select("*, ? as model_type", v.ColumnName).
Limit(take).Offset(offset).Find(&items).Error; err != nil {
2024-03-02 17:23:11 +00:00
return items, err
2024-02-03 07:20:32 +00:00
}
idx := lo.Map(items, func(item *models.Feed, index int) uint {
return item.ID
2024-03-03 14:57:17 +00:00
})
if len(noReact) <= 0 || !noReact[0] {
var reactions []struct {
PostID uint
Symbol string
Count int64
}
if err := database.C.Model(&models.Reaction{}).
Select(v.ColumnName+"_id as post_id, symbol, COUNT(id) as count").
Where(v.ColumnName+"_id IN (?)", idx).
2024-03-03 14:57:17 +00:00
Group("post_id, symbol").
Scan(&reactions).Error; err != nil {
return items, err
}
itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) {
return item.ID, item
2024-03-03 14:57:17 +00:00
})
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
2024-03-03 14:57:17 +00:00
}
}
}
2024-03-10 15:35:38 +00:00
{
var attachments []struct {
models.Attachment
PostID uint `json:"post_id"`
}
itemMap := lo.SliceToMap(items, func(item *models.Feed) (uint, *models.Feed) {
return item.ID, item
})
idx := lo.Map(items, func(item *models.Feed, index int) uint {
return item.ID
})
if err := database.C.
Model(&models.Attachment{}).
Select(v.ColumnName+"_id as post_id, *").
Where(v.ColumnName+"_id IN (?)", idx).
Scan(&attachments).Error; err != nil {
return items, err
}
list := map[uint][]models.Attachment{}
for _, info := range attachments {
list[info.PostID] = append(list[info.PostID], info.Attachment)
}
for k, v := range list {
if post, ok := itemMap[k]; ok {
post.Attachments = v
}
}
}
2024-03-02 17:23:11 +00:00
return items, nil
2024-02-03 07:20:32 +00:00
}
func MapCategoriesAndTags[T models.PostInterface](item T) (T, error) {
2024-02-02 15:42:42 +00:00
var err error
2024-03-02 17:23:11 +00:00
categories := item.GetCategories()
2024-02-02 15:42:42 +00:00
for idx, category := range categories {
2024-02-14 14:03:45 +00:00
categories[idx], err = GetCategory(category.Alias)
2024-02-02 15:42:42 +00:00
if err != nil {
2024-03-02 17:23:11 +00:00
return item, err
2024-02-02 15:42:42 +00:00
}
}
2024-03-02 17:23:11 +00:00
item.SetCategories(categories)
tags := item.GetHashtags()
2024-02-02 15:42:42 +00:00
for idx, tag := range tags {
2024-02-14 14:03:45 +00:00
tags[idx], err = GetTagOrCreate(tag.Alias, tag.Name)
2024-02-02 15:42:42 +00:00
if err != nil {
2024-03-02 17:23:11 +00:00
return item, err
2024-02-02 15:42:42 +00:00
}
}
2024-03-02 17:23:11 +00:00
item.SetHashtags(tags)
return item, nil
}
func NewPost[T models.PostInterface](item T) (T, error) {
item, err := MapCategoriesAndTags(item)
2024-03-02 17:23:11 +00:00
if err != nil {
return item, err
}
2024-02-02 15:42:42 +00:00
2024-03-02 17:23:11 +00:00
if item.GetRealm() != nil {
if !item.GetRealm().IsPublic {
2024-02-09 04:36:39 +00:00
var member models.RealmMember
if err := database.C.Where(&models.RealmMember{
2024-03-02 17:23:11 +00:00
RealmID: item.GetRealm().ID,
AccountID: item.GetAuthor().ID,
2024-02-09 04:36:39 +00:00
}).First(&member).Error; err != nil {
2024-03-02 17:23:11 +00:00
return item, fmt.Errorf("you aren't a part of that realm")
2024-02-09 04:36:39 +00:00
}
}
2024-02-02 15:42:42 +00:00
}
2024-03-02 17:23:11 +00:00
if err := database.C.Save(&item).Error; err != nil {
return item, err
2024-02-03 17:08:31 +00:00
}
2024-03-02 17:23:11 +00:00
if item.GetReplyTo() != nil {
go func() {
var op models.Moment
if err := database.C.Where("id = ?", item.GetReplyTo()).Preload("Author").First(&op).Error; err == nil {
if op.Author.ID != item.GetAuthor().ID {
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), item.GetID())
err := NotifyAccount(
op.Author,
fmt.Sprintf("%s replied you", item.GetAuthor().Name),
fmt.Sprintf("%s replied your post. Check it out!", item.GetAuthor().Name),
&proto.NotifyLink{Label: "Related post", Url: postUrl},
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")
}
}
}
}()
2024-02-02 15:42:42 +00:00
}
2024-03-02 17:23:11 +00:00
var subscribers []models.AccountMembership
if err := database.C.Where(&models.AccountMembership{
FollowingID: item.GetAuthor().ID,
}).Preload("Follower").Find(&subscribers).Error; err == nil && len(subscribers) > 0 {
go func() {
accounts := lo.Map(subscribers, func(item models.AccountMembership, index int) models.Account {
return item.Follower
})
for _, account := range accounts {
postUrl := fmt.Sprintf("https://%s/posts/%d", viper.GetString("domain"), item.GetID())
err := NotifyAccount(
2024-03-02 17:23:11 +00:00
account,
fmt.Sprintf("%s just posted a post", item.GetAuthor().Name),
"Account you followed post a brand new post. Check it out!",
&proto.NotifyLink{Label: "Related post", Url: postUrl},
)
if err != nil {
log.Error().Err(err).Msg("An error occurred when notifying user...")
}
}
2024-03-02 17:23:11 +00:00
}()
}
2024-03-02 17:23:11 +00:00
return item, nil
2024-02-02 15:42:42 +00:00
}
2024-02-02 16:50:23 +00:00
func EditPost[T models.PostInterface](item T) (T, error) {
item, err := MapCategoriesAndTags(item)
2024-03-02 17:23:11 +00:00
if err != nil {
return item, err
2024-02-03 17:08:31 +00:00
}
2024-03-02 17:23:11 +00:00
err = database.C.Save(&item).Error
2024-02-03 17:08:31 +00:00
2024-03-02 17:23:11 +00:00
return item, err
}
2024-02-03 17:08:31 +00:00
func DeletePost[T models.PostInterface](item T) error {
2024-03-02 17:23:11 +00:00
return database.C.Delete(&item).Error
2024-02-03 17:08:31 +00:00
}
func (v *PostTypeContext) React(reaction models.Reaction) (bool, models.Reaction, error) {
if err := database.C.Where(reaction).First(&reaction).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return true, reaction, database.C.Save(&reaction).Error
} else {
return true, reaction, err
}
2024-02-02 16:50:23 +00:00
} else {
return false, reaction, database.C.Delete(&reaction).Error
2024-02-02 16:50:23 +00:00
}
}