2024-02-02 15:42:42 +00:00
|
|
|
package services
|
|
|
|
|
|
|
|
import (
|
2024-02-21 14:58:51 +00:00
|
|
|
"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-03 04:17:18 +00:00
|
|
|
"errors"
|
2024-03-02 17:23:11 +00:00
|
|
|
"fmt"
|
|
|
|
pluralize "github.com/gertd/go-pluralize"
|
|
|
|
"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
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type PostTypeContext[T models.PostInterface] struct {
|
|
|
|
Tx *gorm.DB
|
|
|
|
|
|
|
|
TypeName string
|
|
|
|
CanReply bool
|
|
|
|
CanRepost bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var pluralizeHelper = pluralize.NewClient()
|
|
|
|
|
|
|
|
func (v *PostTypeContext[T]) GetTableName(plural ...bool) string {
|
|
|
|
if len(plural) <= 0 || !plural[0] {
|
|
|
|
return strings.ToLower(v.TypeName)
|
|
|
|
} else {
|
|
|
|
return pluralizeHelper.Plural(strings.ToLower(v.TypeName))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *PostTypeContext[T]) Preload() *PostTypeContext[T] {
|
2024-03-03 04:17:18 +00:00
|
|
|
v.Tx.Preload("Author").
|
|
|
|
Preload("Attachments").
|
|
|
|
Preload("Categories").
|
2024-03-03 13:24:08 +00:00
|
|
|
Preload("Hashtags")
|
2024-03-02 17:23:11 +00:00
|
|
|
|
|
|
|
if v.CanReply {
|
|
|
|
v.Tx.Preload("ReplyTo")
|
|
|
|
}
|
|
|
|
if v.CanRepost {
|
|
|
|
v.Tx.Preload("RepostTo")
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
2024-02-05 07:51:31 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) FilterWithCategory(alias string) *PostTypeContext[T] {
|
|
|
|
table := v.GetTableName()
|
|
|
|
v.Tx.Joins(fmt.Sprintf("JOIN %s_categories ON %s.id = %s_categories.%s_id", table, v.GetTableName(true), table, v.GetTableName())).
|
|
|
|
Joins(fmt.Sprintf("JOIN %s_categories ON %s_categories.id = %s_categories.category_id", table, table, table)).
|
|
|
|
Where(table+"_categories.alias = ?", alias)
|
|
|
|
return v
|
2024-02-07 08:41:35 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) FilterWithTag(alias string) *PostTypeContext[T] {
|
|
|
|
table := v.GetTableName()
|
|
|
|
v.Tx.Joins(fmt.Sprintf("JOIN %s_tags ON %s.id = %s_tags.%s_id", table, v.GetTableName(true), table, v.GetTableName())).
|
|
|
|
Joins(fmt.Sprintf("JOIN %s_tags ON %s_tags.id = %s_tags.category_id", table, table, table)).
|
|
|
|
Where(table+"_tags.alias = ?", alias)
|
|
|
|
return v
|
2024-02-07 08:41:35 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) FilterPublishedAt(date time.Time) *PostTypeContext[T] {
|
|
|
|
v.Tx.Where("published_at <= ? AND published_at IS NULL", date)
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *PostTypeContext[T]) FilterRealm(id uint) *PostTypeContext[T] {
|
|
|
|
if id > 0 {
|
|
|
|
v.Tx = v.Tx.Where("realm_id = ?", id)
|
|
|
|
} else {
|
|
|
|
v.Tx = v.Tx.Where("realm_id IS NULL")
|
2024-02-06 02:57:05 +00:00
|
|
|
}
|
2024-03-02 17:23:11 +00:00
|
|
|
return v
|
|
|
|
}
|
2024-02-06 02:57:05 +00:00
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) FilterAuthor(id uint) *PostTypeContext[T] {
|
|
|
|
v.Tx = v.Tx.Where("author_id = ?", id)
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *PostTypeContext[T]) FilterReply(condition bool) *PostTypeContext[T] {
|
|
|
|
if condition {
|
|
|
|
v.Tx = v.Tx.Where("reply_id IS NOT NULL")
|
|
|
|
} else {
|
|
|
|
v.Tx = v.Tx.Where("reply_id IS NULL")
|
2024-02-06 02:57:05 +00:00
|
|
|
}
|
2024-03-02 17:23:11 +00:00
|
|
|
return v
|
|
|
|
}
|
2024-02-06 02:57:05 +00:00
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) SortCreatedAt(order string) *PostTypeContext[T] {
|
|
|
|
v.Tx.Order(fmt.Sprintf("created_at %s", order))
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
2024-03-03 13:24:08 +00:00
|
|
|
func (v *PostTypeContext[T]) GetViaAlias(alias string) (T, error) {
|
|
|
|
var item T
|
|
|
|
if err := v.Preload().Tx.Where("alias = ?", alias).First(&item).Error; err != nil {
|
|
|
|
return item, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return item, nil
|
|
|
|
}
|
|
|
|
|
2024-03-03 04:17:18 +00:00
|
|
|
func (v *PostTypeContext[T]) Get(id uint) (T, error) {
|
2024-03-02 17:23:11 +00:00
|
|
|
var item T
|
|
|
|
if err := v.Preload().Tx.Where("id = ?", id).First(&item).Error; err != nil {
|
|
|
|
return item, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return item, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *PostTypeContext[T]) Count() (int64, error) {
|
|
|
|
var count int64
|
|
|
|
table := viper.GetString("database.prefix") + v.GetTableName(true)
|
|
|
|
if err := v.Tx.Table(table).Count(&count).Error; err != nil {
|
|
|
|
return count, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return count, nil
|
2024-02-06 02:57:05 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) List(take int, offset int, noReact ...bool) ([]T, error) {
|
2024-02-12 04:32:37 +00:00
|
|
|
if take > 20 {
|
|
|
|
take = 20
|
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
var items []T
|
|
|
|
if err := v.Preload().Tx.Limit(take).Offset(offset).Find(&items).Error; err != nil {
|
|
|
|
return items, err
|
2024-02-03 07:20:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
return items, nil
|
2024-02-03 07:20:32 +00:00
|
|
|
}
|
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) MapCategoriesAndTags(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 (v *PostTypeContext[T]) New(item T) (T, error) {
|
|
|
|
item, err := v.MapCategoriesAndTags(item)
|
|
|
|
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())
|
2024-02-08 10:47:29 +00:00
|
|
|
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!",
|
2024-02-21 14:58:51 +00:00
|
|
|
&proto.NotifyLink{Label: "Related post", Url: postUrl},
|
2024-02-08 10:47:29 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("An error occurred when notifying user...")
|
|
|
|
}
|
|
|
|
}
|
2024-03-02 17:23:11 +00:00
|
|
|
}()
|
2024-02-08 10:47:29 +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
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) Edit(item T) (T, error) {
|
|
|
|
item, err := v.MapCategoriesAndTags(item)
|
|
|
|
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
|
|
|
|
2024-03-02 17:23:11 +00:00
|
|
|
func (v *PostTypeContext[T]) Delete(item T) error {
|
|
|
|
return database.C.Delete(&item).Error
|
2024-02-03 17:08:31 +00:00
|
|
|
}
|
|
|
|
|
2024-03-03 04:17:18 +00:00
|
|
|
func (v *PostTypeContext[T]) 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 {
|
2024-03-03 04:17:18 +00:00
|
|
|
return false, reaction, database.C.Delete(&reaction).Error
|
2024-02-02 16:50:23 +00:00
|
|
|
}
|
|
|
|
}
|