Browse Source

graph2: Added Chapter mutations

1.0
Gisle Aune 6 years ago
parent
commit
a4034b0faa
  1. 4
      graph2/graph.go
  2. 61
      graph2/queries/chapter.go
  3. 4
      graph2/queries/resolver.go
  4. 12
      graph2/schema/root.gql
  5. 6
      graph2/schema/types/Chapter.gql
  6. 1
      internal/auth/permissions.go
  7. 38
      models/chapters/add.go
  8. 25
      models/chapters/db.go
  9. 38
      models/chapters/edit.go
  10. 12
      models/chapters/remove.go

4
graph2/graph.go

@ -22,6 +22,10 @@ func (r *rootResolver) Query() QueryResolver {
return &queries.Resolver
}
func (r *rootResolver) Mutation() MutationResolver {
return queries.MutationResolver
}
func (r *rootResolver) Log() LogResolver {
return &types.LogResolver
}

61
graph2/queries/chapter.go

@ -2,7 +2,13 @@ package queries
import (
"context"
"errors"
"time"
"git.aiterp.net/rpdata/api/internal/auth"
"git.aiterp.net/rpdata/api/models/stories"
"git.aiterp.net/rpdata/api/graph2/input"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/models/chapters"
)
@ -10,3 +16,58 @@ import (
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
return chapters.FindID(id)
}
func (r *mutationResolver) AddChapter(ctx context.Context, input input.ChapterAddInput) (models.Chapter, error) {
story, err := stories.FindID(input.StoryID)
if err != nil {
return models.Chapter{}, errors.New("Story not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() {
return models.Chapter{}, errors.New("Unauthorized")
}
author := token.UserID
if input.Author != nil && *input.Author != author {
if !token.Permitted("story.add") {
return models.Chapter{}, errors.New("False pretender")
}
author = *input.Author
}
if !story.Open && story.Author != author {
return models.Chapter{}, errors.New("Story is not open")
}
return chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate)
}
func (r *mutationResolver) EditChapter(ctx context.Context, input input.ChapterEditInput) (models.Chapter, error) {
chapter, err := chapters.FindID(input.ID)
if err != nil {
return models.Chapter{}, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "story.edit") {
return models.Chapter{}, errors.New("Unauthorized")
}
return chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate)
}
func (r *mutationResolver) RemoveChapter(ctx context.Context, input input.ChapterRemoveInput) (models.Chapter, error) {
chapter, err := chapters.FindID(input.ID)
if err != nil {
return models.Chapter{}, errors.New("Chapter not found")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "story.remove") {
return models.Chapter{}, errors.New("Unauthorized")
}
return chapters.Remove(chapter)
}

4
graph2/queries/resolver.go

@ -1,6 +1,10 @@
package queries
type resolver struct{}
type mutationResolver struct{}
// Resolver has all the queries
var Resolver resolver
// MutationResolver brings the mutagens.
var MutationResolver *mutationResolver

12
graph2/schema/root.gql

@ -1,5 +1,6 @@
schema {
query: Query
mutation: Mutation
}
type Query {
@ -57,6 +58,17 @@ type Query {
token: Token!
}
type Mutation {
# Add a chapter to a story
addChapter(input: ChapterAddInput!): Chapter!
# Edit a chapter
editChapter(input: ChapterEditInput!): Chapter!
# Remove a chapter
removeChapter(input: ChapterRemoveInput!): Chapter!
}
# A Date represents a RFC3339 encoded date with up to millisecond precision.
scalar Date

6
graph2/schema/types/Chapter.gql

@ -56,4 +56,10 @@ input ChapterEditInput {
# Set the fictional date for a chapter
fictionalDate: Date
}
# Input for removeChapter mutation
input ChapterRemoveInput {
# The chapter to remove
id: String!
}

1
internal/auth/permissions.go

@ -18,6 +18,7 @@ func AllPermissions() map[string]string {
"post.edit": "Can edit posts",
"post.move": "Can move posts",
"post.remove": "Can remove posts",
"story.add": "Can add non-owned stories",
"story.edit": "Can edit non-owned stories",
"story.remove": "Can remove non-owned stories",
"story.unlisted": "Can view non-owned unlisted stories",

38
models/chapters/add.go

@ -0,0 +1,38 @@
package chapters
import (
"time"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo/bson"
)
// Add adds a new chapter.
func Add(story models.Story, title, author, source string, createdDate time.Time, finctionalDate *time.Time) (models.Chapter, error) {
chapter := models.Chapter{
ID: makeChapterID(),
StoryID: story.ID,
Title: title,
Author: author,
Source: source,
CreatedDate: createdDate,
EditedDate: createdDate,
}
if finctionalDate != nil {
chapter.FictionalDate = *finctionalDate
}
err := collection.Insert(chapter)
if err != nil {
return models.Chapter{}, err
}
if createdDate.After(story.UpdatedDate) {
if err := storyCollection.UpdateId(story.ID, bson.M{"$set": bson.M{"updatedDate": createdDate}}); err == nil {
story.UpdatedDate = createdDate
}
}
return chapter, nil
}

25
models/chapters/db.go

@ -1,12 +1,17 @@
package chapters
import (
"crypto/rand"
"encoding/binary"
"strconv"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo"
)
var collection *mgo.Collection
var storyCollection *mgo.Collection
func find(query interface{}) (models.Chapter, error) {
chapter := models.Chapter{}
@ -25,9 +30,29 @@ func list(query interface{}) ([]models.Chapter, error) {
return chapters, nil
}
func makeChapterID() string {
result := "SC"
offset := 0
data := make([]byte, 32)
rand.Read(data)
for len(result) < 24 {
result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36)
offset += 8
if offset >= 32 {
rand.Read(data)
offset = 0
}
}
return result[:24]
}
func init() {
store.HandleInit(func(db *mgo.Database) {
collection = db.C("story.chapters")
storyCollection = db.C("story.stories")
collection.EnsureIndexKey("storyId")
collection.EnsureIndexKey("author")

38
models/chapters/edit.go

@ -0,0 +1,38 @@
package chapters
import (
"time"
"git.aiterp.net/rpdata/api/models"
"github.com/globalsign/mgo/bson"
)
// Edit edits a chapter, and updates EditedDate. While many Edit functions cheat if there's nothing to
// change, this functill will due to EditedDate.
func Edit(chapter models.Chapter, title, source *string, fictionalDate *time.Time) (models.Chapter, error) {
now := time.Now()
changes := bson.M{"editedDate": now}
edited := chapter
edited.EditedDate = now
if title != nil && *title != chapter.Title {
changes["title"] = *title
edited.Title = *title
}
if source != nil && *source != chapter.Source {
changes["source"] = *source
edited.Source = *source
}
if fictionalDate != nil && !fictionalDate.Equal(chapter.FictionalDate) {
changes["fictionalDate"] = *fictionalDate
edited.FictionalDate = *fictionalDate
}
err := collection.UpdateId(chapter.ID, bson.M{"$set": changes})
if err != nil {
return chapter, err
}
return edited, nil
}

12
models/chapters/remove.go

@ -0,0 +1,12 @@
package chapters
import "git.aiterp.net/rpdata/api/models"
// Remove removes a chapter.
func Remove(chapter models.Chapter) (models.Chapter, error) {
if err := collection.RemoveId(chapter.ID); err != nil {
return models.Chapter{}, err
}
return chapter, nil
}
Loading…
Cancel
Save