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) } }) }