You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
4.4 KiB
198 lines
4.4 KiB
package log
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
"log"
|
|
"strconv"
|
|
"time"
|
|
|
|
"git.aiterp.net/rpdata/api/internal/store"
|
|
"github.com/globalsign/mgo"
|
|
"github.com/globalsign/mgo/bson"
|
|
)
|
|
|
|
var postCollection *mgo.Collection
|
|
|
|
// A Post is a part of a log file.
|
|
type Post struct {
|
|
ID string `bson:"_id"`
|
|
LogID string `bson:"logId"`
|
|
Time time.Time `bson:"time"`
|
|
Kind string `bson:"kind"`
|
|
Nick string `bson:"nick"`
|
|
Text string `bson:"text"`
|
|
Index int `bson:"index"`
|
|
}
|
|
|
|
// Edit the post
|
|
func (post *Post) Edit(time *time.Time, kind *string, nick *string, text *string) error {
|
|
changes := bson.M{}
|
|
changed := false
|
|
postCopy := *post
|
|
|
|
if time != nil && !time.IsZero() && !time.Equal(post.Time) {
|
|
changes["time"] = *time
|
|
changed = true
|
|
postCopy.Time = *time
|
|
}
|
|
if kind != nil && *kind != "" && *kind != post.Kind {
|
|
changes["kind"] = *kind
|
|
changed = true
|
|
postCopy.Kind = *kind
|
|
}
|
|
if nick != nil && *nick != "" && *nick != post.Nick {
|
|
changes["nick"] = *nick
|
|
changed = true
|
|
postCopy.Nick = *nick
|
|
}
|
|
if text != nil && *text != "" && *text != post.Text {
|
|
changes["text"] = *text
|
|
changed = true
|
|
postCopy.Text = *text
|
|
}
|
|
|
|
if !changed {
|
|
return nil
|
|
}
|
|
|
|
err := postCollection.UpdateId(post.ID, bson.M{"$set": changes})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*post = postCopy
|
|
return nil
|
|
}
|
|
|
|
// Move the post
|
|
func (post *Post) Move(targetIndex int) error {
|
|
if targetIndex < 1 {
|
|
return errors.New("Invalid index")
|
|
}
|
|
|
|
postMutex.Lock()
|
|
defer postMutex.Unlock()
|
|
|
|
// To avoid problems, only allow target indices that are allowed. If it's 1, then there is bound to
|
|
// be a post at the index.
|
|
if targetIndex > 1 {
|
|
existingPost := Post{}
|
|
err := postCollection.Find(bson.M{"logId": post.LogID, "index": targetIndex}).One(&existingPost)
|
|
|
|
if err != nil || existingPost.Index != targetIndex {
|
|
return errors.New("No post found at the index")
|
|
}
|
|
}
|
|
|
|
query := bson.M{"logId": post.LogID}
|
|
operation := bson.M{"$inc": bson.M{"index": 1}}
|
|
|
|
if targetIndex < post.Index {
|
|
query["$and"] = []bson.M{
|
|
bson.M{"index": bson.M{"$gte": targetIndex}},
|
|
bson.M{"index": bson.M{"$lt": post.Index}},
|
|
}
|
|
} else {
|
|
query["$and"] = []bson.M{
|
|
bson.M{"index": bson.M{"$gt": post.Index}},
|
|
bson.M{"index": bson.M{"$lte": targetIndex}},
|
|
}
|
|
|
|
operation["$inc"] = bson.M{"index": -1}
|
|
}
|
|
|
|
_, err := postCollection.UpdateAll(query, operation)
|
|
if err != nil {
|
|
return errors.New("moving others: " + err.Error())
|
|
}
|
|
|
|
err = postCollection.UpdateId(post.ID, bson.M{"$set": bson.M{"index": targetIndex}})
|
|
if err != nil {
|
|
return errors.New("moving: " + err.Error())
|
|
}
|
|
|
|
post.Index = targetIndex
|
|
|
|
return nil
|
|
}
|
|
|
|
// FindPostID finds a log post by ID.
|
|
func FindPostID(id string) (Post, error) {
|
|
return findPost(bson.M{"_id": id})
|
|
}
|
|
|
|
// ListPostIDs lists log posts by ID
|
|
func ListPostIDs(ids ...string) ([]Post, error) {
|
|
return listPosts(bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
// RemovePost removes a post, moving all subsequent post up one index
|
|
func RemovePost(id string) (Post, error) {
|
|
postMutex.Lock()
|
|
defer postMutex.Unlock()
|
|
|
|
post, err := findPost(bson.M{"_id": id})
|
|
if err != nil {
|
|
return Post{}, err
|
|
}
|
|
|
|
err = postCollection.RemoveId(id)
|
|
if err != nil {
|
|
return Post{}, err
|
|
}
|
|
|
|
_, err = postCollection.UpdateAll(bson.M{"logId": post.LogID, "index": bson.M{"$gt": post.Index}}, bson.M{"$inc": bson.M{"index": -1}})
|
|
if err != nil {
|
|
return Post{}, err
|
|
}
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func findPost(query interface{}) (Post, error) {
|
|
post := Post{}
|
|
err := postCollection.Find(query).One(&post)
|
|
if err != nil {
|
|
return Post{}, err
|
|
}
|
|
|
|
return post, nil
|
|
}
|
|
|
|
func listPosts(query interface{}) ([]Post, error) {
|
|
posts := make([]Post, 0, 64)
|
|
err := postCollection.Find(query).All(&posts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return posts, nil
|
|
}
|
|
|
|
// MakePostID makes a random post ID
|
|
func MakePostID(time time.Time) string {
|
|
data := make([]byte, 4)
|
|
rand.Read(data)
|
|
|
|
return "P" + strconv.FormatInt(time.UnixNano(), 36) + strconv.FormatInt(int64(binary.LittleEndian.Uint32(data)), 36)
|
|
}
|
|
|
|
func init() {
|
|
store.HandleInit(func(db *mgo.Database) {
|
|
postCollection = db.C("logbot3.posts")
|
|
|
|
postCollection.EnsureIndexKey("logId")
|
|
postCollection.EnsureIndexKey("time")
|
|
postCollection.EnsureIndexKey("kind")
|
|
postCollection.EnsureIndexKey("index")
|
|
|
|
err := postCollection.EnsureIndex(mgo.Index{
|
|
Key: []string{"$text:text"},
|
|
})
|
|
if err != nil {
|
|
log.Fatalln("init logbot3.logs:", err)
|
|
}
|
|
})
|
|
}
|