2019-06-05 06:00:40 +00:00
|
|
|
package common
|
|
|
|
|
2019-06-16 06:30:58 +00:00
|
|
|
import (
|
2022-02-21 03:53:13 +00:00
|
|
|
"errors"
|
|
|
|
"time"
|
2020-02-10 10:28:33 +00:00
|
|
|
|
2022-02-21 03:53:13 +00:00
|
|
|
//"log"
|
2019-08-14 10:39:04 +00:00
|
|
|
|
2022-02-21 03:53:13 +00:00
|
|
|
"database/sql"
|
|
|
|
"strconv"
|
2019-06-16 06:30:58 +00:00
|
|
|
|
2022-02-21 03:53:13 +00:00
|
|
|
qgen "git.tuxpa.in/a/gosora/query_gen"
|
2019-06-16 06:30:58 +00:00
|
|
|
)
|
2019-06-05 06:00:40 +00:00
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
var Convos ConversationStore
|
2019-06-09 03:21:48 +00:00
|
|
|
var convoStmts ConvoStmts
|
|
|
|
|
|
|
|
type ConvoStmts struct {
|
2022-02-21 03:32:53 +00:00
|
|
|
fetchPost *sql.Stmt
|
|
|
|
getPosts *sql.Stmt
|
|
|
|
countPosts *sql.Stmt
|
|
|
|
edit *sql.Stmt
|
|
|
|
create *sql.Stmt
|
|
|
|
delete *sql.Stmt
|
|
|
|
has *sql.Stmt
|
|
|
|
|
|
|
|
editPost *sql.Stmt
|
|
|
|
createPost *sql.Stmt
|
|
|
|
deletePost *sql.Stmt
|
|
|
|
|
|
|
|
getUsers *sql.Stmt
|
2019-06-09 03:21:48 +00:00
|
|
|
}
|
|
|
|
|
2019-08-14 10:39:04 +00:00
|
|
|
func init() {
|
2022-02-21 03:32:53 +00:00
|
|
|
DbInits.Add(func(acc *qgen.Accumulator) error {
|
|
|
|
cpo := "conversations_posts"
|
|
|
|
convoStmts = ConvoStmts{
|
|
|
|
fetchPost: acc.Select(cpo).Columns("cid,body,post,createdBy").Where("pid=?").Prepare(),
|
|
|
|
getPosts: acc.Select(cpo).Columns("pid,body,post,createdBy").Where("cid=?").Limit("?,?").Prepare(),
|
|
|
|
countPosts: acc.Count(cpo).Where("cid=?").Prepare(),
|
|
|
|
edit: acc.Update("conversations").Set("lastReplyBy=?,lastReplyAt=?").Where("cid=?").Prepare(),
|
|
|
|
create: acc.Insert("conversations").Columns("createdAt,lastReplyAt").Fields("UTC_TIMESTAMP(),UTC_TIMESTAMP()").Prepare(),
|
|
|
|
has: acc.Count("conversations_participants").Where("uid=? AND cid=?").Prepare(),
|
|
|
|
|
|
|
|
editPost: acc.Update(cpo).Set("body=?,post=?").Where("pid=?").Prepare(),
|
|
|
|
createPost: acc.Insert(cpo).Columns("cid,body,post,createdBy").Fields("?,?,?,?").Prepare(),
|
|
|
|
deletePost: acc.Delete(cpo).Where("pid=?").Prepare(),
|
|
|
|
|
|
|
|
getUsers: acc.Select("conversations_participants").Columns("uid").Where("cid=?").Prepare(),
|
|
|
|
}
|
|
|
|
return acc.FirstError()
|
|
|
|
})
|
2019-08-14 10:39:04 +00:00
|
|
|
}
|
2019-06-09 03:21:48 +00:00
|
|
|
|
2019-06-05 06:00:40 +00:00
|
|
|
type Conversation struct {
|
2022-02-21 03:32:53 +00:00
|
|
|
ID int
|
|
|
|
Link string
|
|
|
|
CreatedBy int
|
|
|
|
CreatedAt time.Time
|
|
|
|
LastReplyBy int
|
|
|
|
LastReplyAt time.Time
|
2019-06-16 06:30:58 +00:00
|
|
|
}
|
|
|
|
|
2019-08-14 10:39:04 +00:00
|
|
|
func (co *Conversation) Posts(offset, itemsPerPage int) (posts []*ConversationPost, err error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
rows, err := convoStmts.getPosts.Query(co.ID, offset, itemsPerPage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
p := &ConversationPost{CID: co.ID}
|
|
|
|
err := rows.Scan(&p.ID, &p.Body, &p.Post, &p.CreatedBy)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
p, err = ConvoPostProcess.OnLoad(p)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
posts = append(posts, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return posts, rows.Err()
|
2019-06-09 03:21:48 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
func (co *Conversation) PostsCount() (count int) {
|
2022-02-21 03:32:53 +00:00
|
|
|
return Countf(convoStmts.countPosts, co.ID)
|
2019-06-16 06:30:58 +00:00
|
|
|
}
|
|
|
|
|
2019-08-21 01:31:13 +00:00
|
|
|
func (co *Conversation) Uids() (ids []int, err error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
rows, e := convoStmts.getUsers.Query(co.ID)
|
|
|
|
if e != nil {
|
|
|
|
return nil, e
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
for rows.Next() {
|
|
|
|
var id int
|
|
|
|
if e := rows.Scan(&id); e != nil {
|
|
|
|
return nil, e
|
|
|
|
}
|
|
|
|
ids = append(ids, id)
|
|
|
|
}
|
|
|
|
return ids, rows.Err()
|
2019-08-21 01:31:13 +00:00
|
|
|
}
|
|
|
|
|
2019-08-20 08:44:37 +00:00
|
|
|
func (co *Conversation) Has(uid int) (in bool) {
|
2022-02-21 03:32:53 +00:00
|
|
|
return Countf(convoStmts.has, uid, co.ID) > 0
|
2019-08-20 08:44:37 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
func (co *Conversation) Update() error {
|
2022-02-21 03:32:53 +00:00
|
|
|
_, err := convoStmts.edit.Exec(co.CreatedAt, co.LastReplyBy, co.LastReplyAt, co.ID)
|
|
|
|
return err
|
2019-06-16 06:30:58 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
func (co *Conversation) Create() (int, error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
res, err := convoStmts.create.Exec()
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2019-06-16 06:30:58 +00:00
|
|
|
|
2022-02-21 03:32:53 +00:00
|
|
|
lastID, err := res.LastInsertId()
|
|
|
|
return int(lastID), err
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
2020-02-12 09:11:27 +00:00
|
|
|
func BuildConvoURL(coid int) string {
|
2022-02-21 03:32:53 +00:00
|
|
|
return "/user/convo/" + strconv.Itoa(coid)
|
2020-02-12 09:11:27 +00:00
|
|
|
}
|
|
|
|
|
2019-08-28 06:47:54 +00:00
|
|
|
type ConversationExtra struct {
|
2022-02-21 03:32:53 +00:00
|
|
|
*Conversation
|
|
|
|
Users []*User
|
2019-08-28 06:47:54 +00:00
|
|
|
}
|
|
|
|
|
2019-06-05 06:00:40 +00:00
|
|
|
type ConversationStore interface {
|
2022-02-21 03:32:53 +00:00
|
|
|
Get(id int) (*Conversation, error)
|
|
|
|
GetUser(uid, offset int) (cos []*Conversation, err error)
|
|
|
|
GetUserExtra(uid, offset int) (cos []*ConversationExtra, err error)
|
|
|
|
GetUserCount(uid int) (count int)
|
|
|
|
Delete(id int) error
|
|
|
|
Count() (count int)
|
|
|
|
Create(content string, createdBy int, participants []int) (int, error)
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DefaultConversationStore struct {
|
2022-02-21 03:32:53 +00:00
|
|
|
get *sql.Stmt
|
|
|
|
getUser *sql.Stmt
|
|
|
|
getUserCount *sql.Stmt
|
|
|
|
delete *sql.Stmt
|
|
|
|
deletePosts *sql.Stmt
|
|
|
|
deleteParticipants *sql.Stmt
|
|
|
|
create *sql.Stmt
|
|
|
|
addParticipant *sql.Stmt
|
|
|
|
count *sql.Stmt
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDefaultConversationStore(acc *qgen.Accumulator) (*DefaultConversationStore, error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
co := "conversations"
|
|
|
|
return &DefaultConversationStore{
|
|
|
|
get: acc.Select(co).Columns("createdBy,createdAt,lastReplyBy,lastReplyAt").Where("cid=?").Prepare(),
|
|
|
|
getUser: acc.SimpleInnerJoin("conversations_participants AS cp", "conversations AS c", "cp.cid, c.createdBy, c.createdAt, c.lastReplyBy, c.lastReplyAt", "cp.cid=c.cid", "cp.uid=?", "c.lastReplyAt DESC, c.createdAt DESC, c.cid DESC", "?,?"),
|
|
|
|
getUserCount: acc.Count("conversations_participants").Where("uid=?").Prepare(),
|
|
|
|
delete: acc.Delete(co).Where("cid=?").Prepare(),
|
|
|
|
deletePosts: acc.Delete("conversations_posts").Where("cid=?").Prepare(),
|
|
|
|
deleteParticipants: acc.Delete("conversations_participants").Where("cid=?").Prepare(),
|
|
|
|
create: acc.Insert(co).Columns("createdBy,createdAt,lastReplyBy,lastReplyAt").Fields("?,UTC_TIMESTAMP(),?,UTC_TIMESTAMP()").Prepare(),
|
|
|
|
addParticipant: acc.Insert("conversations_participants").Columns("uid,cid").Fields("?,?").Prepare(),
|
|
|
|
count: acc.Count(co).Prepare(),
|
|
|
|
}, acc.FirstError()
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *DefaultConversationStore) Get(id int) (*Conversation, error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
co := &Conversation{ID: id}
|
|
|
|
err := s.get.QueryRow(id).Scan(&co.CreatedBy, &co.CreatedAt, &co.LastReplyBy, &co.LastReplyAt)
|
|
|
|
co.Link = BuildConvoURL(co.ID)
|
|
|
|
return co, err
|
2019-07-26 23:13:43 +00:00
|
|
|
}
|
|
|
|
|
Cascade delete attachments properly.
Cascade delete replied to topic events for replies properly.
Cascade delete likes on topic posts properly.
Cascade delete replies and their children properly.
Recalculate user stats properly when items are deleted.
Users can now unlike topic opening posts.
Add a recalculator to fix abnormalities across upgrades.
Try fixing a last_ip daily update bug.
Add Existable interface.
Add Delete method to LikeStore.
Add Each, Exists, Create, CountUser, CountMegaUser and CountBigUser methods to ReplyStore.
Add CountUser, CountMegaUser, CountBigUser methods to TopicStore.
Add Each method to UserStore.
Add Add, Delete and DeleteResource methods to SubscriptionStore.
Add Delete, DeleteByParams, DeleteByParamsExtra and AidsByParamsExtra methods to ActivityStream.
Add Exists method to ProfileReplyStore.
Add DropColumn, RenameColumn and ChangeColumn to the database adapters.
Shorten ipaddress column names to ip.
- topics table.
- replies table
- users_replies table.
- polls_votes table.
Add extra column to activity_stream table.
Fix an issue upgrading sites to MariaDB 10.3 from older versions of Gosora. Please report any other issues you find.
You need to run the updater / patcher for this commit.
2020-01-31 07:22:08 +00:00
|
|
|
func (s *DefaultConversationStore) GetUser(uid, offset int) (cos []*Conversation, err error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
rows, err := s.getUser.Query(uid, offset, Config.ItemsPerPage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
co := &Conversation{}
|
|
|
|
err := rows.Scan(&co.ID, &co.CreatedBy, &co.CreatedAt, &co.LastReplyBy, &co.LastReplyAt)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
co.Link = BuildConvoURL(co.ID)
|
|
|
|
cos = append(cos, co)
|
|
|
|
}
|
|
|
|
err = rows.Err()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(cos) == 0 {
|
|
|
|
err = sql.ErrNoRows
|
|
|
|
}
|
|
|
|
return cos, err
|
2019-07-26 23:13:43 +00:00
|
|
|
}
|
|
|
|
|
Cascade delete attachments properly.
Cascade delete replied to topic events for replies properly.
Cascade delete likes on topic posts properly.
Cascade delete replies and their children properly.
Recalculate user stats properly when items are deleted.
Users can now unlike topic opening posts.
Add a recalculator to fix abnormalities across upgrades.
Try fixing a last_ip daily update bug.
Add Existable interface.
Add Delete method to LikeStore.
Add Each, Exists, Create, CountUser, CountMegaUser and CountBigUser methods to ReplyStore.
Add CountUser, CountMegaUser, CountBigUser methods to TopicStore.
Add Each method to UserStore.
Add Add, Delete and DeleteResource methods to SubscriptionStore.
Add Delete, DeleteByParams, DeleteByParamsExtra and AidsByParamsExtra methods to ActivityStream.
Add Exists method to ProfileReplyStore.
Add DropColumn, RenameColumn and ChangeColumn to the database adapters.
Shorten ipaddress column names to ip.
- topics table.
- replies table
- users_replies table.
- polls_votes table.
Add extra column to activity_stream table.
Fix an issue upgrading sites to MariaDB 10.3 from older versions of Gosora. Please report any other issues you find.
You need to run the updater / patcher for this commit.
2020-01-31 07:22:08 +00:00
|
|
|
func (s *DefaultConversationStore) GetUserExtra(uid, offset int) (cos []*ConversationExtra, err error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
raw, err := s.GetUser(uid, offset)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//log.Printf("raw: %+v\n", raw)
|
|
|
|
|
|
|
|
if len(raw) == 1 {
|
|
|
|
//log.Print("r0b2")
|
|
|
|
uids, err := raw[0].Uids()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//log.Println("r1b2")
|
|
|
|
umap, err := Users.BulkGetMap(uids)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//log.Println("r2b2")
|
|
|
|
users := make([]*User, len(umap))
|
|
|
|
var i int
|
|
|
|
for _, user := range umap {
|
|
|
|
users[i] = user
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
return []*ConversationExtra{{raw[0], users}}, nil
|
|
|
|
}
|
|
|
|
//log.Println("1")
|
|
|
|
|
|
|
|
cmap := make(map[int]*ConversationExtra, len(raw))
|
|
|
|
for _, co := range raw {
|
|
|
|
cmap[co.ID] = &ConversationExtra{co, nil}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Use inqbuild for this or a similar function
|
|
|
|
var q string
|
|
|
|
idList := make([]interface{}, len(raw))
|
|
|
|
for i, co := range raw {
|
|
|
|
if i == 0 {
|
|
|
|
q = "?"
|
|
|
|
} else {
|
|
|
|
q += ",?"
|
|
|
|
}
|
|
|
|
idList[i] = strconv.Itoa(co.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
rows, err := qgen.NewAcc().Select("conversations_participants").Columns("uid,cid").Where("cid IN(" + q + ")").Query(idList...)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
//log.Println("2")
|
|
|
|
|
|
|
|
idmap := make(map[int][]int) // cid: []uid
|
|
|
|
puidmap := make(map[int]struct{})
|
|
|
|
for rows.Next() {
|
|
|
|
var uid, cid int
|
|
|
|
err := rows.Scan(&uid, &cid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
idmap[cid] = append(idmap[cid], uid)
|
|
|
|
puidmap[uid] = struct{}{}
|
|
|
|
}
|
|
|
|
if err = rows.Err(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//log.Println("3")
|
|
|
|
//log.Printf("idmap: %+v\n", idmap)
|
|
|
|
//log.Printf("puidmap: %+v\n",puidmap)
|
|
|
|
|
|
|
|
puids := make([]int, len(puidmap))
|
|
|
|
var i int
|
|
|
|
for puid, _ := range puidmap {
|
|
|
|
puids[i] = puid
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
umap, err := Users.BulkGetMap(puids)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
//log.Println("4")
|
|
|
|
//log.Printf("umap: %+v\n", umap)
|
|
|
|
for cid, uids := range idmap {
|
|
|
|
co := cmap[cid]
|
|
|
|
for _, uid := range uids {
|
|
|
|
co.Users = append(co.Users, umap[uid])
|
|
|
|
}
|
|
|
|
//log.Printf("co.Conversation: %+v\n", co.Conversation)
|
|
|
|
//log.Printf("co.Users: %+v\n", co.Users)
|
|
|
|
cmap[cid] = co
|
|
|
|
}
|
|
|
|
//log.Printf("cmap: %+v\n", cmap)
|
|
|
|
for _, ra := range raw {
|
|
|
|
cos = append(cos, cmap[ra.ID])
|
|
|
|
}
|
|
|
|
//log.Printf("cos: %+v\n", cos)
|
|
|
|
|
|
|
|
return cos, rows.Err()
|
2019-08-28 06:47:54 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
func (s *DefaultConversationStore) GetUserCount(uid int) (count int) {
|
2022-02-21 03:32:53 +00:00
|
|
|
err := s.getUserCount.QueryRow(uid).Scan(&count)
|
|
|
|
if err != nil {
|
|
|
|
LogError(err)
|
|
|
|
}
|
|
|
|
return count
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
// TODO: Use a foreign key or transaction
|
2019-06-05 06:00:40 +00:00
|
|
|
func (s *DefaultConversationStore) Delete(id int) error {
|
2022-02-21 03:32:53 +00:00
|
|
|
_, err := s.delete.Exec(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = s.deletePosts.Exec(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
_, err = s.deleteParticipants.Exec(id)
|
|
|
|
return err
|
2019-06-05 06:00:40 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 23:13:43 +00:00
|
|
|
func (s *DefaultConversationStore) Create(content string, createdBy int, participants []int) (int, error) {
|
2022-02-21 03:32:53 +00:00
|
|
|
if len(participants) == 0 {
|
|
|
|
return 0, errors.New("no participants set")
|
|
|
|
}
|
|
|
|
res, err := s.create.Exec(createdBy, createdBy)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
lastID, err := res.LastInsertId()
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
post := &ConversationPost{CID: int(lastID), Body: content, CreatedBy: createdBy}
|
|
|
|
_, err = post.Create()
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range participants {
|
|
|
|
if p == createdBy {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
_, err := s.addParticipant.Exec(p, lastID)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_, err = s.addParticipant.Exec(createdBy, lastID)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return int(lastID), err
|
2019-07-26 23:13:43 +00:00
|
|
|
}
|
|
|
|
|
2019-06-05 06:00:40 +00:00
|
|
|
// Count returns the total number of topics on these forums
|
|
|
|
func (s *DefaultConversationStore) Count() (count int) {
|
2022-02-21 03:32:53 +00:00
|
|
|
err := s.count.QueryRow().Scan(&count)
|
|
|
|
if err != nil {
|
|
|
|
LogError(err)
|
|
|
|
}
|
|
|
|
return count
|
2019-08-14 10:39:04 +00:00
|
|
|
}
|