From 0e0d802a32e31fb990681e9b2399213c39cad4cf Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Thu, 20 Sep 2018 20:50:22 +0200 Subject: [PATCH] graph2: Added Story mutations --- graph2/queries/chapter.go | 4 ++ graph2/queries/story.go | 94 +++++++++++++++++++++++++++++++++++ graph2/schema/root.gql | 16 ++++++ graph2/schema/types/Story.gql | 9 ++++ models/stories/add-tag.go | 29 +++++++++++ models/stories/add.go | 30 +++++++++++ models/stories/edit.go | 45 +++++++++++++++++ models/stories/remove-tag.go | 34 +++++++++++++ models/stories/remove.go | 10 ++++ 9 files changed, 271 insertions(+) create mode 100644 models/stories/add-tag.go create mode 100644 models/stories/add.go create mode 100644 models/stories/edit.go create mode 100644 models/stories/remove-tag.go create mode 100644 models/stories/remove.go diff --git a/graph2/queries/chapter.go b/graph2/queries/chapter.go index 188349e..40312b5 100644 --- a/graph2/queries/chapter.go +++ b/graph2/queries/chapter.go @@ -13,10 +13,14 @@ import ( "git.aiterp.net/rpdata/api/models/chapters" ) +// Queries + func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) { return chapters.FindID(id) } +// Mutations + func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (models.Chapter, error) { story, err := stories.FindID(input.StoryID) if err != nil { diff --git a/graph2/queries/story.go b/graph2/queries/story.go index a4f3457..cf03139 100644 --- a/graph2/queries/story.go +++ b/graph2/queries/story.go @@ -3,7 +3,9 @@ package queries import ( "context" "errors" + "time" + "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/stories" @@ -29,3 +31,95 @@ func (r *resolver) Stories(ctx context.Context, filter *stories.Filter) ([]model return stories.List(filter) } + +// Mutations + +func (r *mutationResolver) AddStory(ctx context.Context, input input.StoryAddInput) (models.Story, error) { + token := auth.TokenFromContext(ctx) + if token == nil || !token.Permitted("member", "story.add") { + return models.Story{}, errors.New("Permission denied") + } + + author := token.UserID + if input.Author != nil && *input.Author != author { + if !token.Permitted("story.add") { + return models.Story{}, errors.New("You are not permitted to add a story in another author's name") + } + + author = *input.Author + } + + fictionalDate := time.Time{} + if input.FictionalDate != nil { + fictionalDate = *input.FictionalDate + } + + listed := input.Listed != nil && *input.Listed + open := input.Open != nil && *input.Open + + return stories.Add(input.Name, author, input.Category, listed, open, input.Tags, time.Now(), fictionalDate) +} + +func (r *mutationResolver) AddStoryTag(ctx context.Context, input input.StoryTagAddInput) (models.Story, error) { + token := auth.TokenFromContext(ctx) + + story, err := stories.FindID(input.ID) + if err != nil { + return models.Story{}, errors.New("Story not found") + } + + if token.PermittedUser(story.Author, "member", "story.edit") { + return models.Story{}, errors.New("You are not permitted to edit this story") + } + + return stories.AddTag(story, input.Tag) +} + +func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input input.StoryTagRemoveInput) (models.Story, error) { + token := auth.TokenFromContext(ctx) + + story, err := stories.FindID(input.ID) + if err != nil { + return models.Story{}, errors.New("Story not found") + } + + if token.PermittedUser(story.Author, "member", "story.edit") { + return models.Story{}, errors.New("You are not permitted to edit this story") + } + + return stories.RemoveTag(story, input.Tag) +} + +func (r *mutationResolver) EditStory(ctx context.Context, input input.StoryEditInput) (models.Story, error) { + token := auth.TokenFromContext(ctx) + + story, err := stories.FindID(input.ID) + if err != nil { + return models.Story{}, errors.New("Story not found") + } + + if token.PermittedUser(story.Author, "member", "story.edit") { + return models.Story{}, errors.New("You are not permitted to remove this story") + } + + if input.ClearFictionalDate != nil && *input.ClearFictionalDate { + input.FictionalDate = &time.Time{} + } + + return stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate) +} + +func (r *mutationResolver) RemoveStory(ctx context.Context, input input.StoryRemoveInput) (models.Story, error) { + token := auth.TokenFromContext(ctx) + + story, err := stories.FindID(input.ID) + if err != nil { + return models.Story{}, errors.New("Story not found") + } + + if token.PermittedUser(story.Author, "member", "story.remove") { + return models.Story{}, errors.New("You are not permitted to remove this story") + } + + return stories.Remove(story) +} diff --git a/graph2/schema/root.gql b/graph2/schema/root.gql index 0b51517..9de5b22 100644 --- a/graph2/schema/root.gql +++ b/graph2/schema/root.gql @@ -59,6 +59,22 @@ type Query { } type Mutation { + # Add a story + addStory(input: StoryAddInput!): Story! + + # Add a story tag + addStoryTag(input: StoryTagAddInput!): Story! + + # Remove a story tag + removeStoryTag(input: StoryTagRemoveInput!): Story! + + # Edit a story + editStory(input: StoryEditInput!): Story! + + # Remove a story + removeStory(input: StoryRemoveInput!): Story! + + # Add a chapter to a story addChapter(input: ChapterAddInput!): Chapter! diff --git a/graph2/schema/types/Story.gql b/graph2/schema/types/Story.gql index e30ea8b..57ba0ef 100644 --- a/graph2/schema/types/Story.gql +++ b/graph2/schema/types/Story.gql @@ -112,6 +112,9 @@ input StoryEditInput { # Set the fictional date of the story. fictionalDate: Date + + # Clear the fictional date of the story. + clearFictionalDate: Boolean } # Input for the addStoryTag mutation @@ -132,6 +135,12 @@ input StoryTagRemoveInput { tag: TagInput! } +# Input for the removeStory mutation +input StoryRemoveInput { + # What story to remove. + id: String! +} + # Possible values for Story.category enum StoryCategory { # General information diff --git a/models/stories/add-tag.go b/models/stories/add-tag.go new file mode 100644 index 0000000..55a189e --- /dev/null +++ b/models/stories/add-tag.go @@ -0,0 +1,29 @@ +package stories + +import ( + "errors" + + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo/bson" +) + +// ErrTagAlreadyExists is an error returned by Story.AddTag +var ErrTagAlreadyExists = errors.New("Tag already exists on story") + +// AddTag adds a tag to the story. It returns ErrTagAlreadyExists if the tag is already there +func AddTag(story models.Story, tag models.Tag) (models.Story, error) { + for i := range story.Tags { + if story.Tags[i].Equal(tag) { + return models.Story{}, ErrTagAlreadyExists + } + } + + err := collection.UpdateId(story.ID, bson.M{"$push": bson.M{"tags": tag}}) + if err != nil { + return models.Story{}, err + } + + story.Tags = append(story.Tags, tag) + + return story, nil +} diff --git a/models/stories/add.go b/models/stories/add.go new file mode 100644 index 0000000..9d0b4bd --- /dev/null +++ b/models/stories/add.go @@ -0,0 +1,30 @@ +package stories + +import ( + "time" + + "git.aiterp.net/rpdata/api/models" +) + +// Add creates a new story. +func Add(name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (models.Story, error) { + story := models.Story{ + ID: makeStoryID(), + Name: name, + Author: author, + Category: category, + Listed: listed, + Open: open, + Tags: tags, + CreatedDate: createdDate, + FictionalDate: fictionalDate, + UpdatedDate: createdDate, + } + + err := collection.Insert(story) + if err != nil { + return models.Story{}, err + } + + return story, nil +} diff --git a/models/stories/edit.go b/models/stories/edit.go new file mode 100644 index 0000000..fae96c8 --- /dev/null +++ b/models/stories/edit.go @@ -0,0 +1,45 @@ +package stories + +import ( + "time" + + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo/bson" +) + +// Edit edits the story and returns the edited story if it succeeds. +func Edit(story models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (models.Story, error) { + changes := bson.M{} + + if name != nil && *name != story.Name { + changes["name"] = *name + story.Name = *name + } + if category != nil && *category != story.Category { + changes["category"] = *category + story.Category = *category + } + if listed != nil && *listed != story.Listed { + changes["listed"] = *listed + story.Listed = *listed + } + if open != nil && *open != story.Open { + changes["open"] = *open + story.Open = *open + } + if fictionalDate != nil && !fictionalDate.Equal(story.FictionalDate) { + changes["fictionalDate"] = *fictionalDate + story.FictionalDate = *fictionalDate + } + + if len(changes) == 0 { + return story, nil + } + + err := collection.UpdateId(story.ID, bson.M{"$set": changes}) + if err != nil { + return models.Story{}, err + } + + return story, nil +} diff --git a/models/stories/remove-tag.go b/models/stories/remove-tag.go new file mode 100644 index 0000000..ec7300b --- /dev/null +++ b/models/stories/remove-tag.go @@ -0,0 +1,34 @@ +package stories + +import ( + "errors" + + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo/bson" +) + +// ErrTagNotExists is an error returned by Story.RemoveTag +var ErrTagNotExists = errors.New("Tag does not exist on story") + +// RemoveTag removes a tag to the story. It returns ErrTagNotExists if the tag does not exist. +func RemoveTag(story models.Story, tag models.Tag) (models.Story, error) { + index := -1 + for i := range story.Tags { + if story.Tags[i].Equal(tag) { + index = i + break + } + } + if index == -1 { + return models.Story{}, ErrTagNotExists + } + + err := collection.UpdateId(story.ID, bson.M{"$pull": bson.M{"tags": tag}}) + if err != nil { + return models.Story{}, err + } + + story.Tags = append(story.Tags[:index], story.Tags[index+1:]...) + + return story, nil +} diff --git a/models/stories/remove.go b/models/stories/remove.go new file mode 100644 index 0000000..7f2a157 --- /dev/null +++ b/models/stories/remove.go @@ -0,0 +1,10 @@ +package stories + +import ( + "git.aiterp.net/rpdata/api/models" +) + +// Remove the story from the database +func Remove(story models.Story) (models.Story, error) { + return story, collection.RemoveId(story.ID) +}