From 34f86d5037e1b11687923c5ee2ca96087a1f4a95 Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Tue, 9 Oct 2018 20:25:55 +0200 Subject: [PATCH] graph2: Added logging changes for Channel, Log, Post and Character. --- graph2/queries/channel.go | 19 +++++++++++++-- graph2/queries/character.go | 48 ++++++++++++++++++++++++++++++++----- graph2/queries/log.go | 28 +++++++++++++++++++--- graph2/queries/post.go | 32 ++++++++++++++++++++++--- models/change.go | 33 +++++++++++++++++++++++++ models/changes/db.go | 31 ++++++++++++++++++++++++ models/changes/submit.go | 48 +++++++++++++++++++++++++++++++++++++ 7 files changed, 225 insertions(+), 14 deletions(-) create mode 100644 models/change.go create mode 100644 models/changes/db.go create mode 100644 models/changes/submit.go diff --git a/graph2/queries/channel.go b/graph2/queries/channel.go index a9476af..58e46f2 100644 --- a/graph2/queries/channel.go +++ b/graph2/queries/channel.go @@ -7,6 +7,7 @@ import ( "git.aiterp.net/rpdata/api/graph2/input" "git.aiterp.net/rpdata/api/internal/auth" "git.aiterp.net/rpdata/api/models" + "git.aiterp.net/rpdata/api/models/changes" "git.aiterp.net/rpdata/api/models/channels" ) @@ -45,7 +46,14 @@ func (r *mutationResolver) AddChannel(ctx context.Context, input input.ChannelAd locationName = *input.LocationName } - return channels.Add(input.Name, logged, hub, eventName, locationName) + channel, err := channels.Add(input.Name, logged, hub, eventName, locationName) + if err != nil { + return models.Channel{}, errors.New("Failed to add channel: " + err.Error()) + } + + go changes.Submit("Channel", "add", token.UserID, channel) + + return channel, nil } func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelEditInput) (models.Channel, error) { @@ -59,5 +67,12 @@ func (r *mutationResolver) EditChannel(ctx context.Context, input input.ChannelE return models.Channel{}, errors.New("Channel not found") } - return channels.Edit(channel, input.Logged, input.Hub, input.EventName, input.LocationName) + channel, err = channels.Edit(channel, input.Logged, input.Hub, input.EventName, input.LocationName) + if err != nil { + return models.Channel{}, errors.New("Failed to edit channel: " + err.Error()) + } + + go changes.Submit("Channel", "edit", token.UserID, channel) + + return channel, nil } diff --git a/graph2/queries/character.go b/graph2/queries/character.go index d45d9d0..2e9cc6c 100644 --- a/graph2/queries/character.go +++ b/graph2/queries/character.go @@ -8,6 +8,7 @@ import ( "git.aiterp.net/rpdata/api/graph2/input" "git.aiterp.net/rpdata/api/internal/auth" "git.aiterp.net/rpdata/api/models" + "git.aiterp.net/rpdata/api/models/changes" "git.aiterp.net/rpdata/api/models/characters" "git.aiterp.net/rpdata/api/models/logs" ) @@ -62,7 +63,14 @@ func (r *mutationResolver) AddCharacter(ctx context.Context, input input.Charact logs.ScheduleFullUpdate() - return characters.Add(input.Nick, input.Name, shortName, author, description) + character, err := characters.Add(input.Nick, input.Name, shortName, author, description) + if err != nil { + return models.Character{}, errors.New("Adding character failed: " + err.Error()) + } + + go changes.Submit("Character", "add", token.UserID, character) + + return character, nil } func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.CharacterNickInput) (models.Character, error) { @@ -82,7 +90,15 @@ func (r *mutationResolver) AddCharacterNick(ctx context.Context, input input.Cha logs.ScheduleFullUpdate() - return characters.AddNick(character, input.Nick) + character, err = characters.AddNick(character, input.Nick) + if err != nil { + return models.Character{}, errors.New("Failed to add nick: " + err.Error()) + } + + go logs.ScheduleFullUpdate() + go changes.Submit("Character", "edit", token.UserID, character) + + return character, nil } func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input.CharacterNickInput) (models.Character, error) { @@ -96,9 +112,15 @@ func (r *mutationResolver) RemoveCharacterNick(ctx context.Context, input input. return models.Character{}, errors.New("You are not permitted to edit this character") } - logs.ScheduleFullUpdate() + character, err = characters.RemoveNick(character, input.Nick) + if err != nil { + return models.Character{}, errors.New("Failed to remove nick: " + err.Error()) + } + + go logs.ScheduleFullUpdate() + go changes.Submit("Character", "edit", token.UserID, character) - return characters.RemoveNick(character, input.Nick) + return character, nil } func (r *mutationResolver) EditCharacter(ctx context.Context, input input.CharacterEditInput) (models.Character, error) { @@ -119,7 +141,14 @@ func (r *mutationResolver) EditCharacter(ctx context.Context, input input.Charac return models.Character{}, errors.New("You are not permitted to edit this character") } - return characters.Edit(character, input.Name, input.ShortName, input.Description) + character, err = characters.Edit(character, input.Name, input.ShortName, input.Description) + if err != nil { + return models.Character{}, errors.New("Failed to edit character: " + err.Error()) + } + + go changes.Submit("Character", "edit", token.UserID, character) + + return character, nil } func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.CharacterRemoveInput) (models.Character, error) { @@ -133,5 +162,12 @@ func (r *mutationResolver) RemoveCharacter(ctx context.Context, input input.Char return models.Character{}, errors.New("You are not permitted to remove this character") } - return characters.Remove(character) + character, err = characters.Remove(character) + if err != nil { + return models.Character{}, errors.New("Failed to remove character: " + err.Error()) + } + + go changes.Submit("Character", "remove", token.UserID, character) + + return character, nil } diff --git a/graph2/queries/log.go b/graph2/queries/log.go index abb4f1b..9fdddf0 100644 --- a/graph2/queries/log.go +++ b/graph2/queries/log.go @@ -9,6 +9,7 @@ import ( "git.aiterp.net/rpdata/api/internal/auth" "git.aiterp.net/rpdata/api/internal/loader" "git.aiterp.net/rpdata/api/models" + "git.aiterp.net/rpdata/api/models/changes" "git.aiterp.net/rpdata/api/models/logs" "github.com/99designs/gqlgen/graphql" ) @@ -71,7 +72,14 @@ func (r *mutationResolver) AddLog(ctx context.Context, input input.LogAddInput) description = *input.Description } - return logs.Add(input.Date, input.Channel, title, event, description, open) + log, err := logs.Add(input.Date, input.Channel, title, event, description, open) + if !token.Authenticated() || !token.Permitted("log.add") { + return models.Log{}, errors.New("Failed to create log: " + err.Error()) + } + + go changes.Submit("Log", "add", token.UserID, log) + + return log, nil } func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput) (models.Log, error) { @@ -85,7 +93,14 @@ func (r *mutationResolver) EditLog(ctx context.Context, input input.LogEditInput return models.Log{}, errors.New("Log not found") } - return logs.Edit(log, input.Title, input.Event, input.Description, input.Open) + log, err = logs.Edit(log, input.Title, input.Event, input.Description, input.Open) + if err != nil { + return models.Log{}, errors.New("Failed to edit log: " + err.Error()) + } + + go changes.Submit("Log", "edit", token.UserID, log) + + return log, nil } func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveInput) (models.Log, error) { @@ -99,5 +114,12 @@ func (r *mutationResolver) RemoveLog(ctx context.Context, input input.LogRemoveI return models.Log{}, errors.New("Log not found") } - return logs.Remove(log) + log, err = logs.Remove(log) + if err != nil { + return models.Log{}, errors.New("Failed to remove log: " + err.Error()) + } + + go changes.Submit("Log", "remove", token.UserID, log) + + return log, nil } diff --git a/graph2/queries/post.go b/graph2/queries/post.go index 86d3797..3ed64cc 100644 --- a/graph2/queries/post.go +++ b/graph2/queries/post.go @@ -4,6 +4,7 @@ import ( "context" "errors" + "git.aiterp.net/rpdata/api/models/changes" "git.aiterp.net/rpdata/api/models/logs" "git.aiterp.net/rpdata/api/graph2/input" @@ -58,6 +59,7 @@ func (r *mutationResolver) AddPost(ctx context.Context, input input.PostAddInput } go logs.UpdateCharacters(log) + go changes.Submit("Post", "add", token.UserID, post) return post, nil } @@ -84,7 +86,14 @@ func (r *mutationResolver) EditPost(ctx context.Context, input input.PostEditInp }() } - return posts.Edit(post, input.Time, input.Kind, input.Nick, input.Text) + post, err = posts.Edit(post, input.Time, input.Kind, input.Nick, input.Text) + if err != nil { + return models.Post{}, errors.New("Adding post failed: " + err.Error()) + } + + go changes.Submit("Post", "edit", token.UserID, post) + + return post, nil } func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInput) ([]models.Post, error) { @@ -98,7 +107,18 @@ func (r *mutationResolver) MovePost(ctx context.Context, input input.PostMoveInp return nil, errors.New("Post not found") } - return posts.Move(post, input.ToPosition) + posts, err := posts.Move(post, input.ToPosition) + if err != nil { + return nil, errors.New("Moving posts failed: " + err.Error()) + } + + posts2 := make([]interface{}, len(posts)) + for i := range posts { + posts2[i] = posts[i] + } + go changes.Submit("Post", "move", token.UserID, posts2...) + + return posts, nil } func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemoveInput) (models.Post, error) { @@ -112,6 +132,11 @@ func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemov return models.Post{}, errors.New("Post not found (before removing, of course)") } + post, err = posts.Remove(post) + if err != nil { + return models.Post{}, errors.New("Could not remove post: " + err.Error()) + } + go func() { log, err := logs.FindID(post.LogID) if err != nil { @@ -120,6 +145,7 @@ func (r *mutationResolver) RemovePost(ctx context.Context, input input.PostRemov logs.UpdateCharacters(log) }() + go changes.Submit("Post", "remove", token.UserID, post) - return posts.Remove(post) + return post, nil } diff --git a/models/change.go b/models/change.go new file mode 100644 index 0000000..f920814 --- /dev/null +++ b/models/change.go @@ -0,0 +1,33 @@ +package models + +import "time" + +// Change represents a change in the rpdata history through the API. +type Change struct { + ID string `bson:"_id"` + Model string `bson:"model"` + Op string `bson:"op"` + Author string `bson:"author"` + Date time.Time `bson:"date"` + + Logs []Log `bson:"logs"` + Characters []Character `bson:"characters"` + Posts []Post `bson:"posts"` +} + +// Data makes a combined, mixed array of all the models stored in this change. +func (change *Change) Data() []interface{} { + data := make([]interface{}, 0, len(change.Logs)+len(change.Characters)+len(change.Posts)) + + for _, log := range change.Logs { + data = append(data, log) + } + for _, character := range change.Characters { + data = append(data, character) + } + for _, post := range change.Posts { + data = append(data, post) + } + + return data +} diff --git a/models/changes/db.go b/models/changes/db.go new file mode 100644 index 0000000..beb46b1 --- /dev/null +++ b/models/changes/db.go @@ -0,0 +1,31 @@ +package changes + +import ( + "log" + "sync" + "time" + + "git.aiterp.net/rpdata/api/internal/store" + "github.com/globalsign/mgo" +) + +var collection *mgo.Collection +var submitMutex sync.Mutex + +func init() { + store.HandleInit(func(db *mgo.Database) { + collection = db.C("common.changes") + + collection.EnsureIndexKey("date") + collection.EnsureIndexKey("author") + + err := collection.EnsureIndex(mgo.Index{ + Name: "expiry", + Key: []string{"date"}, + ExpireAfter: time.Hour * 2400, // 100 days + }) + if err != nil { + log.Fatalln(err) + } + }) +} diff --git a/models/changes/submit.go b/models/changes/submit.go new file mode 100644 index 0000000..b1ed757 --- /dev/null +++ b/models/changes/submit.go @@ -0,0 +1,48 @@ +package changes + +import ( + "strconv" + "time" + + "git.aiterp.net/rpdata/api/internal/counter" + "git.aiterp.net/rpdata/api/models" +) + +// Submit a change to the database +func Submit(model, op, author string, objects ...interface{}) (models.Change, error) { + submitMutex.Lock() + defer submitMutex.Unlock() + + id, err := counter.Next("auto_increment", "Change") + if err != nil { + return models.Change{}, err + } + + change := models.Change{ + ID: "Change_" + strconv.Itoa(id), + Model: model, + Date: time.Now(), + Op: op, + Author: author, + } + + for _, object := range objects { + switch object := object.(type) { + case models.Log: + change.Logs = append(change.Logs, object) + case models.Character: + change.Characters = append(change.Characters, object) + case models.Post: + change.Posts = append(change.Posts, object) + } + } + + // TODO: Push to subscribers + + err = collection.Insert(&change) + if err != nil { + return models.Change{}, err + } + + return change, nil +}