2017-09-03 04:50:31 +00:00
/ *
*
* Reply Resources File
2019-08-31 22:34:43 +00:00
* Copyright Azareal 2016 - 2020
2017-09-03 04:50:31 +00:00
*
* /
2017-11-10 03:33:11 +00:00
package common
2016-12-02 07:38:54 +00:00
2017-11-02 02:52:21 +00:00
import (
2017-11-11 04:06:16 +00:00
"database/sql"
2017-11-02 02:52:21 +00:00
"errors"
2018-01-20 06:50:29 +00:00
"html"
2017-11-02 02:52:21 +00:00
"time"
2017-11-11 04:06:16 +00:00
2019-10-06 11:32:00 +00:00
qgen "github.com/Azareal/Gosora/query_gen"
2017-11-02 02:52:21 +00:00
)
2017-10-12 03:24:14 +00:00
2017-09-28 22:16:34 +00:00
type ReplyUser struct {
2019-05-17 08:40:41 +00:00
Reply
2019-12-08 03:40:56 +00:00
ContentHtml string
2018-12-27 05:42:41 +00:00
UserLink string
CreatedByName string
2019-12-08 03:40:56 +00:00
Avatar string
MicroAvatar string
ClassName string
Tag string
URL string
//URLPrefix string
//URLName string
Level int
2019-05-17 08:40:41 +00:00
ActionIcon string
2018-12-31 09:03:49 +00:00
Attachments [ ] * MiniAttachment
2019-10-06 11:32:00 +00:00
Deletable bool
2016-12-02 07:38:54 +00:00
}
2017-02-10 13:39:13 +00:00
2017-09-28 22:16:34 +00:00
type Reply struct {
2018-12-27 05:42:41 +00:00
ID int
ParentID int
Content string
CreatedBy int
Group int
CreatedAt time . Time
LastEdit int
LastEditBy int
ContentLines int
2019-10-06 11:32:00 +00:00
IP string
2018-12-27 05:42:41 +00:00
Liked bool
LikeCount int
2019-05-17 08:40:41 +00:00
AttachCount int
ActionType string
2017-02-28 09:27:28 +00:00
}
2017-10-12 03:24:14 +00:00
var ErrAlreadyLiked = errors . New ( "You already liked this!" )
2017-11-11 04:06:16 +00:00
var replyStmts ReplyStmts
type ReplyStmts struct {
isLiked * sql . Stmt
createLike * sql . Stmt
2018-01-20 06:50:29 +00:00
edit * sql . Stmt
2018-01-25 04:57:33 +00:00
setPoll * sql . Stmt
2017-11-11 04:06:16 +00:00
delete * sql . Stmt
addLikesToReply * sql . Stmt
removeRepliesFromTopic * sql . Stmt
2020-01-15 04:16:10 +00:00
deleteLikesForReply * sql . Stmt
deleteActivity * sql . Stmt
deleteActivitySubs * sql . Stmt
2020-01-14 10:29:23 +00:00
updateTopicReplies * sql . Stmt
updateTopicReplies2 * sql . Stmt
2017-11-11 04:06:16 +00:00
}
func init ( ) {
2017-11-12 03:29:05 +00:00
DbInits . Add ( func ( acc * qgen . Accumulator ) error {
2020-01-14 05:07:00 +00:00
re := "replies"
2017-11-11 04:06:16 +00:00
replyStmts = ReplyStmts {
2020-01-14 05:07:00 +00:00
isLiked : acc . Select ( "likes" ) . Columns ( "targetItem" ) . Where ( "sentBy=? and targetItem=? and targetType='replies'" ) . Prepare ( ) ,
2017-11-11 08:46:30 +00:00
createLike : acc . Insert ( "likes" ) . Columns ( "weight, targetItem, targetType, sentBy" ) . Fields ( "?,?,?,?" ) . Prepare ( ) ,
2020-01-14 10:29:23 +00:00
edit : acc . Update ( re ) . Set ( "content=?,parsed_content=?" ) . Where ( "rid=? AND poll=0" ) . Prepare ( ) ,
setPoll : acc . Update ( re ) . Set ( "poll=?" ) . Where ( "rid=? AND poll=0" ) . Prepare ( ) ,
delete : acc . Delete ( re ) . Where ( "rid=?" ) . Prepare ( ) ,
addLikesToReply : acc . Update ( re ) . Set ( "likeCount=likeCount+?" ) . Where ( "rid=?" ) . Prepare ( ) ,
removeRepliesFromTopic : acc . Update ( "topics" ) . Set ( "postCount=postCount-?" ) . Where ( "tid=?" ) . Prepare ( ) ,
2020-01-15 04:16:10 +00:00
deleteLikesForReply : acc . Delete ( "likes" ) . Where ( "targetItem=? AND targetType='replies'" ) . Prepare ( ) ,
deleteActivity : acc . Delete ( "activity_stream" ) . Where ( "elementID=? AND elementType='post'" ) . Prepare ( ) ,
deleteActivitySubs : acc . Delete ( "activity_subscriptions" ) . Where ( "targetID=? AND targetType='post'" ) . Prepare ( ) ,
2020-01-14 10:29:23 +00:00
// TODO: Optimise this to avoid firing an update if it's not the last reply in a topic. We will need to set lastReplyID properly in other places and in the patcher first so we can use it here.
updateTopicReplies : acc . RawPrepare ( "UPDATE topics t INNER JOIN replies r ON t.tid = r.tid SET t.lastReplyBy = r.createdBy, t.lastReplyAt = r.createdAt, t.lastReplyID = r.rid WHERE t.tid = ?" ) ,
updateTopicReplies2 : acc . Update ( "topics" ) . Set ( "lastReplyAt=createdAt,lastReplyBy=createdBy,lastReplyID=0" ) . Where ( "postCount=1 AND tid=?" ) . Prepare ( ) ,
2017-11-11 04:06:16 +00:00
}
return acc . FirstError ( )
} )
}
2017-10-12 03:24:14 +00:00
// TODO: Write tests for this
// TODO: Wrap these queries in a transaction to make sure the state is consistent
2019-08-31 22:34:43 +00:00
func ( r * Reply ) Like ( uid int ) ( err error ) {
2017-10-12 03:24:14 +00:00
var rid int // unused, just here to avoid mutating reply.ID
2019-08-31 22:34:43 +00:00
err = replyStmts . isLiked . QueryRow ( uid , r . ID ) . Scan ( & rid )
2017-10-12 03:24:14 +00:00
if err != nil && err != ErrNoRows {
return err
} else if err != ErrNoRows {
return ErrAlreadyLiked
}
score := 1
2019-08-31 22:34:43 +00:00
_ , err = replyStmts . createLike . Exec ( score , r . ID , "replies" , uid )
2017-10-12 03:24:14 +00:00
if err != nil {
return err
}
2019-08-31 22:34:43 +00:00
_ , err = replyStmts . addLikesToReply . Exec ( 1 , r . ID )
2018-03-31 05:25:27 +00:00
if err != nil {
return err
}
2019-10-06 22:20:37 +00:00
_ , err = userStmts . incLiked . Exec ( 1 , uid )
2019-08-31 22:34:43 +00:00
_ = Rstore . GetCache ( ) . Remove ( r . ID )
2017-10-12 03:24:14 +00:00
return err
}
2020-01-14 05:07:00 +00:00
// TODO: Refresh topic list?
2020-01-15 04:16:10 +00:00
// TODO: Restructure alerts so we can delete the "x replied to topic" ones too.
2019-08-31 22:34:43 +00:00
func ( r * Reply ) Delete ( ) error {
2020-01-15 04:16:10 +00:00
creator , err := Users . Get ( r . CreatedBy )
if err == nil {
wcount := WordCount ( r . Content )
err = creator . DecreasePostStats ( wcount , false )
if err != nil {
return err
}
} else if err != ErrNoRows {
return err
}
_ , err = replyStmts . delete . Exec ( r . ID )
2017-10-12 03:24:14 +00:00
if err != nil {
return err
}
2017-11-11 04:06:16 +00:00
// TODO: Move this bit to *Topic
2019-08-31 22:34:43 +00:00
_ , err = replyStmts . removeRepliesFromTopic . Exec ( 1 , r . ParentID )
2020-01-14 10:29:23 +00:00
if err != nil {
return err
}
_ , err = replyStmts . updateTopicReplies . Exec ( r . ParentID )
if err != nil {
return err
}
_ , err = replyStmts . updateTopicReplies2 . Exec ( r . ParentID )
2020-01-14 05:07:00 +00:00
tc := Topics . GetCache ( )
if tc != nil {
tc . Remove ( r . ParentID )
2017-10-12 03:24:14 +00:00
}
2019-08-31 22:34:43 +00:00
_ = Rstore . GetCache ( ) . Remove ( r . ID )
2020-01-15 04:16:10 +00:00
if err != nil {
return err
}
_ , err = replyStmts . deleteLikesForReply . Exec ( r . ID )
if err != nil {
return err
}
_ , err = replyStmts . deleteActivitySubs . Exec ( r . ID )
if err != nil {
return err
}
_ , err = replyStmts . deleteActivity . Exec ( r . ID )
2017-10-12 03:24:14 +00:00
return err
}
2019-08-31 22:34:43 +00:00
func ( r * Reply ) SetPost ( content string ) error {
topic , err := r . Topic ( )
2018-01-20 06:50:29 +00:00
if err != nil {
return err
}
content = PreparseMessage ( html . UnescapeString ( content ) )
2019-12-08 03:40:56 +00:00
parsedContent := ParseMessage ( content , topic . ParentID , "forums" , nil )
2019-08-31 22:34:43 +00:00
_ , err = replyStmts . edit . Exec ( content , parsedContent , r . ID ) // TODO: Sniff if this changed anything to see if we hit an existing poll
_ = Rstore . GetCache ( ) . Remove ( r . ID )
2018-01-20 06:50:29 +00:00
return err
}
2019-05-17 08:40:41 +00:00
// TODO: Write tests for this
2019-08-31 22:34:43 +00:00
func ( r * Reply ) SetPoll ( pollID int ) error {
_ , err := replyStmts . setPoll . Exec ( pollID , r . ID ) // TODO: Sniff if this changed anything to see if we hit a poll
_ = Rstore . GetCache ( ) . Remove ( r . ID )
2018-01-25 04:57:33 +00:00
return err
2018-01-23 10:48:44 +00:00
}
2019-08-31 22:34:43 +00:00
func ( r * Reply ) Topic ( ) ( * Topic , error ) {
return Topics . Get ( r . ParentID )
2017-11-11 04:06:16 +00:00
}
2019-08-31 22:34:43 +00:00
func ( r * Reply ) GetID ( ) int {
return r . ID
2018-02-03 05:47:14 +00:00
}
2019-08-31 22:34:43 +00:00
func ( r * Reply ) GetTable ( ) string {
2018-02-03 05:47:14 +00:00
return "replies"
}
Added Quick Topic.
Added Attachments.
Added Attachment Media Embeds.
Renamed a load of *Store and *Cache methods to reduce the amount of unneccesary typing.
Added petabytes as a unit and cleaned up a few of the friendly units.
Refactored the username change logic to make it easier to maintain.
Refactored the avatar change logic to make it easier to maintain.
Shadow now uses CSS Variables for most of it's colours. We have plans to transpile this to support older browsers later on!
Snuck some CSS Variables into Tempra Conflux.
Added the GroupCache interface to MemoryGroupStore.
Added the Length method to MemoryGroupStore.
Added support for a site short name.
Added the UploadFiles permission.
Renamed more functions.
Fixed the background for the left gutter on the postbit for Tempra Simple and Shadow.
Added support for if statements operating on int8, int16, int32, int32, int64, uint, uint8, uint16, uint32, uint64, float32, and float64 for the template compiler.
Added support for if statements operating on slices and maps for the template compiler.
Fixed a security exploit in reply editing.
Fixed a bug in the URL detector in the parser where it couldn't find URLs with non-standard ports.
Fixed buttons having blue outlines on focus on Shadow.
Refactored the topic creation logic to make it easier to maintain.
Made a few responsive fixes, but there's still more to do in the following commits!
2017-10-05 10:20:28 +00:00
// Copy gives you a non-pointer concurrency safe copy of the reply
2019-08-31 22:34:43 +00:00
func ( r * Reply ) Copy ( ) Reply {
return * r
2017-09-28 22:16:34 +00:00
}