2024-02-02 15:42:42 +00:00
package services
import (
2024-03-05 15:40:54 +00:00
"errors"
"fmt"
2024-05-25 09:43:26 +00:00
"time"
2024-06-22 09:29:53 +00:00
"git.solsynth.dev/hydrogen/interactive/pkg/internal/database"
"git.solsynth.dev/hydrogen/interactive/pkg/internal/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
)
2024-07-27 17:49:16 +00:00
func FilterPostWithUserContext ( tx * gorm . DB , user * models . Account ) * gorm . DB {
if user == nil {
return tx . Where ( "visibility = ?" , models . PostVisibilityAll )
}
2024-07-29 15:33:55 +00:00
// TODO Blocked by dealer, need support get friend list
tx = tx . Where (
"visibility != ? OR (visibility = ? AND visible_users @> '[?]'::jsonb) OR (visibility = ? AND NOT invisible_users @> '[?]'::jsonb) OR visibility != ?" ,
models . PostVisibilityFriends ,
models . PostVisibilitySelected ,
user . ID ,
models . PostVisibilitySelected ,
user . ID ,
models . PostVisibilityNone ,
)
2024-07-27 17:49:16 +00:00
return tx
}
2024-05-15 11:45:49 +00:00
func FilterPostWithCategory ( tx * gorm . DB , alias string ) * gorm . DB {
2024-07-07 05:57:31 +00:00
prefix := viper . GetString ( "database.prefix" )
return tx . Joins ( fmt . Sprintf ( "JOIN %spost_categories ON %sposts.id = %spost_categories.post_id" , prefix , prefix , prefix ) ) .
2024-07-07 06:09:52 +00:00
Joins ( fmt . Sprintf ( "JOIN %scategories ON %scategories.id = %spost_categories.category_id" , prefix , prefix , prefix ) ) .
Where ( fmt . Sprintf ( "%scategories.alias = ?" , prefix ) , alias )
2024-02-07 08:41:35 +00:00
}
2024-05-15 11:45:49 +00:00
func FilterPostWithTag ( tx * gorm . DB , alias string ) * gorm . DB {
2024-07-07 05:57:31 +00:00
prefix := viper . GetString ( "database.prefix" )
return tx . Joins ( fmt . Sprintf ( "JOIN %spost_tags ON %sposts.id = %spost_tags.post_id" , prefix , prefix , prefix ) ) .
2024-07-07 06:09:52 +00:00
Joins ( fmt . Sprintf ( "JOIN %stags ON %stags.id = %spost_tags.tag_id" , prefix , prefix , prefix ) ) .
Where ( fmt . Sprintf ( "%stags.alias = ?" , prefix ) , alias )
2024-02-07 08:41:35 +00:00
}
2024-07-03 14:16:23 +00:00
func FilterPostWithRealm ( tx * gorm . DB , id uint ) * gorm . DB {
2024-03-02 17:23:11 +00:00
if id > 0 {
2024-05-15 11:45:49 +00:00
return tx . Where ( "realm_id = ?" , id )
2024-03-02 17:23:11 +00:00
} else {
2024-05-15 11:45:49 +00:00
return tx . Where ( "realm_id IS NULL" )
2024-02-06 02:57:05 +00:00
}
2024-03-02 17:23:11 +00:00
}
2024-02-06 02:57:05 +00:00
2024-05-15 11:45:49 +00:00
func FilterPostReply ( tx * gorm . DB , replyTo ... uint ) * gorm . DB {
if len ( replyTo ) > 0 && replyTo [ 0 ] > 0 {
return tx . Where ( "reply_id = ?" , replyTo [ 0 ] )
2024-03-02 17:23:11 +00:00
} else {
2024-05-15 11:45:49 +00:00
return tx . Where ( "reply_id IS NULL" )
2024-02-06 02:57:05 +00:00
}
2024-03-02 17:23:11 +00:00
}
2024-02-06 02:57:05 +00:00
2024-05-15 11:45:49 +00:00
func FilterPostWithPublishedAt ( tx * gorm . DB , date time . Time ) * gorm . DB {
2024-07-21 17:44:04 +00:00
return tx .
Where ( "published_at <= ? OR published_at IS NULL" , date ) .
Where ( "published_until > ? OR published_until IS NULL" , date )
2024-03-02 17:23:11 +00:00
}
2024-07-03 14:16:23 +00:00
func FilterPostWithAuthorDraft ( tx * gorm . DB , uid uint ) * gorm . DB {
return tx . Where ( "author_id = ? AND is_draft = ?" , uid , true )
}
func FilterPostDraft ( tx * gorm . DB ) * gorm . DB {
2024-07-05 12:56:25 +00:00
return tx . Where ( "is_draft = ? OR is_draft IS NULL" , false )
2024-07-03 14:16:23 +00:00
}
2024-07-23 08:12:19 +00:00
func PreloadGeneral ( tx * gorm . DB ) * gorm . DB {
return tx .
2024-07-07 03:34:37 +00:00
Preload ( "Tags" ) .
Preload ( "Categories" ) .
2024-06-22 17:18:12 +00:00
Preload ( "Realm" ) .
2024-05-15 11:45:49 +00:00
Preload ( "Author" ) .
2024-05-15 12:31:25 +00:00
Preload ( "ReplyTo" ) .
Preload ( "ReplyTo.Author" ) .
2024-07-07 04:31:05 +00:00
Preload ( "ReplyTo.Tags" ) .
Preload ( "ReplyTo.Categories" ) .
2024-05-15 12:31:25 +00:00
Preload ( "RepostTo" ) .
Preload ( "RepostTo.Author" ) .
2024-07-07 04:31:05 +00:00
Preload ( "RepostTo.Tags" ) .
2024-07-23 08:12:19 +00:00
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 ) .
2024-05-15 11:45:49 +00:00
First ( & item ) . Error ; err != nil {
2024-03-10 15:35:38 +00:00
return item , err
}
2024-03-02 17:23:11 +00:00
return item , nil
}
2024-05-15 11:45:49 +00:00
func CountPost ( tx * gorm . DB ) ( int64 , error ) {
2024-03-02 17:23:11 +00:00
var count int64
2024-05-15 11:45:49 +00:00
if err := tx . Model ( & models . Post { } ) . Count ( & count ) . Error ; err != nil {
2024-03-02 17:23:11 +00:00
return count , err
}
return count , nil
2024-02-06 02:57:05 +00:00
}
2024-05-15 11:45:49 +00:00
func CountPostReply ( id uint ) int64 {
2024-03-25 11:47:51 +00:00
var count int64
2024-05-15 11:45:49 +00:00
if err := database . C . Model ( & models . Post { } ) .
Where ( "reply_id = ?" , id ) .
2024-03-25 11:47:51 +00:00
Count ( & count ) . Error ; err != nil {
return 0
}
return count
}
2024-05-15 11:45:49 +00:00
func CountPostReactions ( id uint ) int64 {
2024-03-25 11:47:51 +00:00
var count int64
if err := database . C . Model ( & models . Reaction { } ) .
2024-05-15 11:45:49 +00:00
Where ( "post_id = ?" , id ) .
2024-03-25 11:47:51 +00:00
Count ( & count ) . Error ; err != nil {
return 0
}
return count
}
2024-07-23 08:12:19 +00:00
func ListPost ( tx * gorm . DB , take int , offset int , order any , noReact ... bool ) ( [ ] * models . Post , error ) {
2024-07-16 08:52:00 +00:00
if take > 100 {
take = 100
2024-02-12 04:32:37 +00:00
}
2024-05-19 12:15:28 +00:00
var items [ ] * models . Post
2024-07-23 08:12:19 +00:00
if err := PreloadGeneral ( tx ) .
2024-05-15 11:45:49 +00:00
Limit ( take ) . Offset ( offset ) .
2024-07-23 08:12:19 +00:00
Order ( order ) .
2024-05-15 11:45:49 +00:00
Find ( & items ) . Error ; err != nil {
2024-03-02 17:23:11 +00:00
return items , err
2024-02-03 07:20:32 +00:00
}
2024-05-19 12:15:28 +00:00
idx := lo . Map ( items , func ( item * models . Post , index int ) uint {
2024-03-05 15:40:54 +00:00
return item . ID
2024-03-03 14:57:17 +00:00
} )
2024-07-03 14:16:23 +00:00
// Load reactions
2024-03-03 14:57:17 +00:00
if len ( noReact ) <= 0 || ! noReact [ 0 ] {
2024-07-05 13:06:18 +00:00
if mapping , err := BatchListResourceReactions ( database . C . Where ( "post_id IN ?" , idx ) , "post_id" ) ; err != nil {
2024-03-03 14:57:17 +00:00
return items , err
2024-07-03 14:16:23 +00:00
} else {
itemMap := lo . SliceToMap ( items , func ( item * models . Post ) ( uint , * models . Post ) {
return item . ID , item
} )
for k , v := range mapping {
if post , ok := itemMap [ k ] ; ok {
2024-07-16 08:52:00 +00:00
post . Metric = models . PostMetric {
ReactionList : v ,
}
2024-07-03 14:16:23 +00:00
}
2024-03-03 14:57:17 +00:00
}
}
}
2024-07-03 14:16:23 +00:00
// Load replies
2024-05-25 09:43:26 +00:00
if len ( noReact ) <= 0 || ! noReact [ 0 ] {
var replies [ ] struct {
PostID uint
Count int64
}
if err := database . C . Model ( & models . Post { } ) .
Select ( "id as post_id, COUNT(id) as count" ) .
Where ( "reply_id IN (?)" , idx ) .
Group ( "post_id" ) .
Scan ( & replies ) . Error ; err != nil {
return items , err
}
itemMap := lo . SliceToMap ( items , func ( item * models . Post ) ( uint , * models . Post ) {
return item . ID , item
} )
list := map [ uint ] int64 { }
for _ , info := range replies {
list [ info . PostID ] = info . Count
}
for k , v := range list {
if post , ok := itemMap [ k ] ; ok {
2024-07-16 08:52:00 +00:00
post . Metric = models . PostMetric {
ReactionList : post . Metric . ReactionList ,
ReplyCount : v ,
}
2024-05-25 09:43:26 +00:00
}
}
}
2024-03-02 17:23:11 +00:00
return items , nil
2024-02-03 07:20:32 +00:00
}
2024-07-03 14:16:23 +00:00
func EnsurePostCategoriesAndTags ( item models . Post ) ( models . Post , error ) {
2024-02-02 15:42:42 +00:00
var err error
2024-05-15 11:45:49 +00:00
for idx , category := range item . Categories {
item . 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-05-15 11:45:49 +00:00
for idx , tag := range item . Tags {
item . 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
return item , nil
}
2024-05-15 11:45:49 +00:00
func NewPost ( user models . Account , item models . Post ) ( models . Post , error ) {
2024-07-28 13:36:47 +00:00
log . Debug ( ) . Any ( "body" , item . Body ) . Msg ( "Posting a post..." )
start := time . Now ( )
log . Debug ( ) . Any ( "tags" , item . Tags ) . Any ( "categories" , item . Categories ) . Msg ( "Preparing categories and tags..." )
2024-07-03 14:16:23 +00:00
item , err := EnsurePostCategoriesAndTags ( item )
2024-03-02 17:23:11 +00:00
if err != nil {
return item , err
}
2024-02-02 15:42:42 +00:00
2024-05-15 11:45:49 +00:00
if item . RealmID != nil {
2024-07-28 13:36:47 +00:00
log . Debug ( ) . Uint ( "id" , * item . RealmID ) . Msg ( "Looking for post author realm..." )
2024-05-15 11:45:49 +00:00
_ , err := GetRealmMember ( * item . RealmID , user . ExternalID )
2024-05-04 14:22:58 +00:00
if err != nil {
return item , fmt . Errorf ( "you aren't a part of that realm: %v" , err )
2024-02-09 04:36:39 +00:00
}
2024-02-02 15:42:42 +00:00
}
2024-07-28 13:36:47 +00:00
log . Debug ( ) . Msg ( "Saving post record into database..." )
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-05-15 11:45:49 +00:00
// Notify the original poster its post has been replied
if item . ReplyID != nil {
2024-06-08 04:33:10 +00:00
var op models . Post
if err := database . C .
Where ( "id = ?" , item . ReplyID ) .
Preload ( "Author" ) .
First ( & op ) . Error ; err == nil {
if op . Author . ID != user . ID {
2024-07-28 13:36:47 +00:00
log . Debug ( ) . Uint ( "user" , op . AuthorID ) . Msg ( "Notifying the original poster their post got replied..." )
2024-07-16 02:53:02 +00:00
err = NotifyPosterAccount (
2024-06-08 04:33:10 +00:00
op . Author ,
2024-07-16 02:53:02 +00:00
"Post got replied" ,
2024-07-27 17:49:16 +00:00
fmt . Sprintf ( "%s (%s) replied your post (#%d)." , user . Nick , user . Name , op . ID ) ,
2024-07-16 02:53:02 +00:00
lo . ToPtr ( fmt . Sprintf ( "%s replied you" , user . Nick ) ) ,
2024-06-08 04:33:10 +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-06-08 04:33:10 +00:00
}
2024-02-02 15:42:42 +00:00
}
2024-07-28 13:36:47 +00:00
log . Debug ( ) . Dur ( "elapsed" , time . Since ( start ) ) . Msg ( "The post is posted." )
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-05-15 11:45:49 +00:00
func EditPost ( item models . Post ) ( models . Post , error ) {
2024-07-27 17:49:16 +00:00
item . EditedAt = lo . ToPtr ( time . Now ( ) )
2024-07-03 14:16:23 +00:00
item , err := EnsurePostCategoriesAndTags ( 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
2024-05-15 11:45:49 +00:00
func DeletePost ( item models . Post ) error {
2024-03-02 17:23:11 +00:00
return database . C . Delete ( & item ) . Error
2024-02-03 17:08:31 +00:00
}
2024-06-08 04:33:10 +00:00
func ReactPost ( user models . Account , reaction models . Reaction ) ( bool , models . Reaction , error ) {
2024-07-23 08:12:19 +00:00
var op models . Post
if err := database . C .
Where ( "id = ?" , reaction . PostID ) .
Preload ( "Author" ) .
First ( & op ) . Error ; err != nil {
return true , reaction , err
}
2024-03-03 04:17:18 +00:00
if err := database . C . Where ( reaction ) . First ( & reaction ) . Error ; err != nil {
if errors . Is ( err , gorm . ErrRecordNotFound ) {
2024-07-23 08:12:19 +00:00
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..." )
2024-06-08 04:33:10 +00:00
}
}
2024-07-23 08:12:19 +00:00
err = database . C . Save ( & reaction ) . Error
2024-07-26 14:21:31 +00:00
if err == nil && reaction . Attitude != models . AttitudeNeutral {
2024-07-23 08:12:19 +00:00
_ = ModifyPosterVoteCount ( op . Author , reaction . Attitude == models . AttitudePositive , 1 )
2024-07-26 13:09:53 +00:00
if reaction . Attitude == models . AttitudePositive {
op . TotalUpvote ++
} else {
op . TotalDownvote ++
}
database . C . Save ( & op )
2024-07-23 08:12:19 +00:00
}
return true , reaction , err
2024-03-03 04:17:18 +00:00
} else {
return true , reaction , err
}
2024-02-02 16:50:23 +00:00
} else {
2024-07-23 08:12:19 +00:00
err = database . C . Delete ( & reaction ) . Error
2024-07-26 14:21:31 +00:00
if err == nil && reaction . Attitude != models . AttitudeNeutral {
2024-07-23 08:12:19 +00:00
_ = ModifyPosterVoteCount ( op . Author , reaction . Attitude == models . AttitudePositive , - 1 )
2024-07-26 13:09:53 +00:00
if reaction . Attitude == models . AttitudePositive {
op . TotalUpvote --
} else {
op . TotalDownvote --
}
database . C . Save ( & op )
2024-07-23 08:12:19 +00:00
}
return false , reaction , err
2024-02-02 16:50:23 +00:00
}
}
2024-07-25 14:58:47 +00:00
func PinPost ( post models . Post ) ( bool , error ) {
if post . PinnedAt != nil {
post . PinnedAt = nil
} else {
post . PinnedAt = lo . ToPtr ( time . Now ( ) )
}
if err := database . C . Save ( & post ) . Error ; err != nil {
return post . PinnedAt != nil , err
}
return post . PinnedAt != nil , nil
}