GraphQL API and utilities for the rpdata project
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

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"`
Position int `bson:"position"`
}
// 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(toPosition int) error {
if toPosition < 1 {
return errors.New("Invalid position")
}
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 position.
if toPosition > 1 {
existingPost := Post{}
err := postCollection.Find(bson.M{"logId": post.LogID, "position": toPosition}).One(&existingPost)
if err != nil || existingPost.Position != toPosition {
return errors.New("No post found at the position")
}
}
query := bson.M{"logId": post.LogID}
operation := bson.M{"$inc": bson.M{"position": 1}}
if toPosition < post.Position {
query["$and"] = []bson.M{
bson.M{"position": bson.M{"$gte": toPosition}},
bson.M{"position": bson.M{"$lt": post.Position}},
}
} else {
query["$and"] = []bson.M{
bson.M{"position": bson.M{"$gt": post.Position}},
bson.M{"position": bson.M{"$lte": toPosition}},
}
operation["$inc"] = bson.M{"position": -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{"position": toPosition}})
if err != nil {
return errors.New("moving: " + err.Error())
}
post.Position = toPosition
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 position
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, "position": bson.M{"$gt": post.Position}}, bson.M{"$inc": bson.M{"position": -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("position")
err := postCollection.EnsureIndex(mgo.Index{
Key: []string{"$text:text"},
})
if err != nil {
log.Fatalln("init logbot3.logs:", err)
}
})
}