05c2ac3ce4
Add the ActivityStream interface to abstract Get, Add and Count. Rename the GlobalCount methods to Count for simplicity. Simplify the variable names in the Count methods. Rename the GlobalCount method to Count and rename the original Count method to CountUser in LoginLogStore. Add a float64 case for bunit, sort of. Theme.RunTmpl now returns ErrBadDefaultTemplate instead of panicking when an interpreted template doesn't exist. Widget.Allowed now checks the zoneid. Fire the alert off in the background in AddActivityAndNotifyTarget instead of blocking the request. Use ErrBadDefaultTemplate instead of calling DefaultTemplates.Lookup directly for custom pages. Split the page struct for the debug page into multiple structs to make things more organised. Add the Count method to ProfileReplyStore. Add the Count method to ReplyStore. Add the DirSize utility function. Add a few ActivityStream tests. Secret gallery stuff.
240 lines
6.6 KiB
Go
240 lines
6.6 KiB
Go
/*
|
|
*
|
|
* Gosora Alerts System
|
|
* Copyright Azareal 2017 - 2020
|
|
*
|
|
*/
|
|
package common
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
//"fmt"
|
|
|
|
"github.com/Azareal/Gosora/common/phrases"
|
|
"github.com/Azareal/Gosora/query_gen"
|
|
)
|
|
|
|
type Alert struct {
|
|
ASID int
|
|
ActorID int
|
|
TargetUserID int
|
|
Event string
|
|
ElementType string
|
|
ElementID int
|
|
CreatedAt time.Time
|
|
|
|
Actor *User
|
|
}
|
|
|
|
type AlertStmts struct {
|
|
notifyWatchers *sql.Stmt
|
|
notifyOne *sql.Stmt
|
|
getWatchers *sql.Stmt
|
|
}
|
|
|
|
var alertStmts AlertStmts
|
|
|
|
// TODO: Move these statements into some sort of activity abstraction
|
|
// TODO: Rewrite the alerts logic
|
|
func init() {
|
|
DbInits.Add(func(acc *qgen.Accumulator) error {
|
|
alertStmts = AlertStmts{
|
|
notifyWatchers: acc.SimpleInsertInnerJoin(
|
|
qgen.DBInsert{"activity_stream_matches", "watcher, asid", ""},
|
|
qgen.DBJoin{"activity_stream", "activity_subscriptions", "activity_subscriptions.user, activity_stream.asid", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""},
|
|
),
|
|
notifyOne: acc.Insert("activity_stream_matches").Columns("watcher, asid").Fields("?,?").Prepare(),
|
|
getWatchers: acc.SimpleInnerJoin("activity_stream", "activity_subscriptions", "activity_subscriptions.user", "activity_subscriptions.targetType = activity_stream.elementType AND activity_subscriptions.targetID = activity_stream.elementID AND activity_subscriptions.user != activity_stream.actor", "asid = ?", "", ""),
|
|
}
|
|
return acc.FirstError()
|
|
})
|
|
}
|
|
|
|
// TODO: See if we can json.Marshal instead?
|
|
func escapeTextInJson(in string) string {
|
|
in = strings.Replace(in, "\"", "\\\"", -1)
|
|
return strings.Replace(in, "/", "\\/", -1)
|
|
}
|
|
|
|
func BuildAlert(alert Alert, user User /* The current user */) (out string, err error) {
|
|
var targetUser *User
|
|
if alert.Actor == nil {
|
|
alert.Actor, err = Users.Get(alert.ActorID)
|
|
if err != nil {
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_no_actor"))
|
|
}
|
|
}
|
|
|
|
/*if alert.ElementType != "forum" {
|
|
targetUser, err = users.Get(alert.TargetUserID)
|
|
if err != nil {
|
|
LocalErrorJS("Unable to find the target user",w,r)
|
|
return
|
|
}
|
|
}*/
|
|
if alert.Event == "friend_invite" {
|
|
return buildAlertString(".new_friend_invite", []string{alert.Actor.Name}, alert.Actor.Link, alert.Actor.Avatar, alert.ASID), nil
|
|
}
|
|
|
|
// Not that many events for us to handle in a forum
|
|
if alert.ElementType == "forum" {
|
|
if alert.Event == "reply" {
|
|
topic, err := Topics.Get(alert.ElementID)
|
|
if err != nil {
|
|
DebugLogf("Unable to find linked topic %d", alert.ElementID)
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic"))
|
|
}
|
|
// Store the forum ID in the targetUser column instead of making a new one? o.O
|
|
// Add an additional column for extra information later on when we add the ability to link directly to posts. We don't need the forum data for now...
|
|
return buildAlertString(".forum_new_topic", []string{alert.Actor.Name, topic.Title}, topic.Link, alert.Actor.Avatar, alert.ASID), nil
|
|
}
|
|
return buildAlertString(".forum_unknown_action", []string{alert.Actor.Name}, "", alert.Actor.Avatar, alert.ASID), nil
|
|
}
|
|
|
|
var url, area string
|
|
var phraseName = "." + alert.ElementType
|
|
switch alert.ElementType {
|
|
case "topic":
|
|
topic, err := Topics.Get(alert.ElementID)
|
|
if err != nil {
|
|
DebugLogf("Unable to find linked topic %d", alert.ElementID)
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic"))
|
|
}
|
|
url = topic.Link
|
|
area = topic.Title
|
|
if alert.TargetUserID == user.ID {
|
|
phraseName += "_own"
|
|
}
|
|
case "user":
|
|
targetUser, err = Users.Get(alert.ElementID)
|
|
if err != nil {
|
|
DebugLogf("Unable to find target user %d", alert.ElementID)
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_no_target_user"))
|
|
}
|
|
area = targetUser.Name
|
|
url = targetUser.Link
|
|
if alert.TargetUserID == user.ID {
|
|
phraseName += "_own"
|
|
}
|
|
case "post":
|
|
topic, err := TopicByReplyID(alert.ElementID)
|
|
if err != nil {
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_no_linked_topic_by_reply"))
|
|
}
|
|
url = topic.Link
|
|
area = topic.Title
|
|
if alert.TargetUserID == user.ID {
|
|
phraseName += "_own"
|
|
}
|
|
default:
|
|
return "", errors.New(phrases.GetErrorPhrase("alerts_invalid_elementtype"))
|
|
}
|
|
|
|
switch alert.Event {
|
|
case "like":
|
|
phraseName += "_like"
|
|
case "mention":
|
|
phraseName += "_mention"
|
|
case "reply":
|
|
phraseName += "_reply"
|
|
}
|
|
|
|
return buildAlertString(phraseName, []string{alert.Actor.Name, area}, url, alert.Actor.Avatar, alert.ASID), nil
|
|
}
|
|
|
|
func buildAlertString(msg string, sub []string, path string, avatar string, asid int) string {
|
|
var substring string
|
|
for _, item := range sub {
|
|
substring += "\"" + escapeTextInJson(item) + "\","
|
|
}
|
|
if len(substring) > 0 {
|
|
substring = substring[:len(substring)-1]
|
|
}
|
|
|
|
return `{"msg":"` + escapeTextInJson(msg) + `","sub":[` + substring + `],"path":"` + escapeTextInJson(path) + `","avatar":"` + escapeTextInJson(avatar) + `","id":` + strconv.Itoa(asid) + `}`
|
|
}
|
|
|
|
func AddActivityAndNotifyAll(actor int, targetUser int, event string, elementType string, elementID int) error {
|
|
id, err := Activity.Add(Alert{ActorID: actor, TargetUserID: targetUser, Event: event, ElementType: elementType, ElementID: elementID})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return NotifyWatchers(id)
|
|
}
|
|
|
|
func AddActivityAndNotifyTarget(alert Alert) error {
|
|
id, err := Activity.Add(alert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = NotifyOne(alert.TargetUserID, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
alert.ASID = id
|
|
|
|
// Live alerts, if the target is online and WebSockets is enabled
|
|
if EnableWebsockets {
|
|
go func() {
|
|
_ = WsHub.pushAlert(alert.TargetUserID, alert)
|
|
//fmt.Println("err:",err)
|
|
}()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func NotifyOne(watcher int, asid int) error {
|
|
_, err := alertStmts.notifyOne.Exec(watcher, asid)
|
|
return err
|
|
}
|
|
|
|
func NotifyWatchers(asid int) error {
|
|
_, err := alertStmts.notifyWatchers.Exec(asid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Alert the subscribers about this without blocking us from doing something else
|
|
if EnableWebsockets {
|
|
go notifyWatchers(asid)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func notifyWatchers(asid int) {
|
|
rows, err := alertStmts.getWatchers.Query(asid)
|
|
if err != nil && err != ErrNoRows {
|
|
LogError(err)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var uid int
|
|
var uids []int
|
|
for rows.Next() {
|
|
err := rows.Scan(&uid)
|
|
if err != nil {
|
|
LogError(err)
|
|
return
|
|
}
|
|
uids = append(uids, uid)
|
|
}
|
|
err = rows.Err()
|
|
if err != nil {
|
|
LogError(err)
|
|
return
|
|
}
|
|
|
|
alert, err := Activity.Get(asid)
|
|
if err != nil && err != ErrNoRows {
|
|
LogError(err)
|
|
return
|
|
}
|
|
_ = WsHub.pushAlerts(uids, alert)
|
|
}
|