@@ -22,6 +22,10 @@ func (r *rootResolver) Query() QueryResolver {
|
|||||||
return &queries.Resolver
|
return &queries.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *rootResolver) Mutation() MutationResolver {
|
||||||
|
return queries.MutationResolver
|
||||||
|
}
|
||||||
|
|
||||||
func (r *rootResolver) Log() LogResolver {
|
func (r *rootResolver) Log() LogResolver {
|
||||||
return &types.LogResolver
|
return &types.LogResolver
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,13 @@ package queries
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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"
|
||||||
"git.aiterp.net/rpdata/api/models/chapters"
|
"git.aiterp.net/rpdata/api/models/chapters"
|
||||||
)
|
)
|
||||||
@@ -10,3 +16,58 @@ import (
|
|||||||
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
|
func (r *resolver) Chapter(ctx context.Context, id string) (models.Chapter, error) {
|
||||||
return chapters.FindID(id)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package queries
|
package queries
|
||||||
|
|
||||||
type resolver struct{}
|
type resolver struct{}
|
||||||
|
type mutationResolver struct{}
|
||||||
|
|
||||||
// Resolver has all the queries
|
// Resolver has all the queries
|
||||||
var Resolver resolver
|
var Resolver resolver
|
||||||
|
|
||||||
|
// MutationResolver brings the mutagens.
|
||||||
|
var MutationResolver *mutationResolver
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
|
mutation: Mutation
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
@@ -57,6 +58,17 @@ type Query {
|
|||||||
token: Token!
|
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.
|
# A Date represents a RFC3339 encoded date with up to millisecond precision.
|
||||||
scalar Date
|
scalar Date
|
||||||
|
|
||||||
|
|||||||
@@ -56,4 +56,10 @@ input ChapterEditInput {
|
|||||||
|
|
||||||
# Set the fictional date for a chapter
|
# Set the fictional date for a chapter
|
||||||
fictionalDate: Date
|
fictionalDate: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
# Input for removeChapter mutation
|
||||||
|
input ChapterRemoveInput {
|
||||||
|
# The chapter to remove
|
||||||
|
id: String!
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@ func AllPermissions() map[string]string {
|
|||||||
"post.edit": "Can edit posts",
|
"post.edit": "Can edit posts",
|
||||||
"post.move": "Can move posts",
|
"post.move": "Can move posts",
|
||||||
"post.remove": "Can remove posts",
|
"post.remove": "Can remove posts",
|
||||||
|
"story.add": "Can add non-owned stories",
|
||||||
"story.edit": "Can edit non-owned stories",
|
"story.edit": "Can edit non-owned stories",
|
||||||
"story.remove": "Can remove non-owned stories",
|
"story.remove": "Can remove non-owned stories",
|
||||||
"story.unlisted": "Can view non-owned unlisted stories",
|
"story.unlisted": "Can view non-owned unlisted stories",
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
package chapters
|
package chapters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.aiterp.net/rpdata/api/internal/store"
|
"git.aiterp.net/rpdata/api/internal/store"
|
||||||
"git.aiterp.net/rpdata/api/models"
|
"git.aiterp.net/rpdata/api/models"
|
||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
var collection *mgo.Collection
|
var collection *mgo.Collection
|
||||||
|
var storyCollection *mgo.Collection
|
||||||
|
|
||||||
func find(query interface{}) (models.Chapter, error) {
|
func find(query interface{}) (models.Chapter, error) {
|
||||||
chapter := models.Chapter{}
|
chapter := models.Chapter{}
|
||||||
@@ -25,9 +30,29 @@ func list(query interface{}) ([]models.Chapter, error) {
|
|||||||
return chapters, nil
|
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() {
|
func init() {
|
||||||
store.HandleInit(func(db *mgo.Database) {
|
store.HandleInit(func(db *mgo.Database) {
|
||||||
collection = db.C("story.chapters")
|
collection = db.C("story.chapters")
|
||||||
|
storyCollection = db.C("story.stories")
|
||||||
|
|
||||||
collection.EnsureIndexKey("storyId")
|
collection.EnsureIndexKey("storyId")
|
||||||
collection.EnsureIndexKey("author")
|
collection.EnsureIndexKey("author")
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user