This commit is contained in:
@@ -19,6 +19,9 @@ type Database interface {
|
||||
Tags() repositories.TagRepository
|
||||
Logs() repositories.LogRepository
|
||||
Posts() repositories.PostRepository
|
||||
Stories() repositories.StoryRepository
|
||||
Chapters() repositories.ChapterRepository
|
||||
Comments() repositories.CommentRepository
|
||||
|
||||
Close(ctx context.Context) error
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ func (r *changeRepository) List(ctx context.Context, filter models.ChangeFilter)
|
||||
}
|
||||
if len(filter.Keys) > 0 {
|
||||
query["keys"] = bson.M{"$in": filter.Keys}
|
||||
} else {
|
||||
query["listed"] = true
|
||||
}
|
||||
if filter.Author != nil && *filter.Author != "" {
|
||||
query["author"] = *filter.Author
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
)
|
||||
|
||||
type chapterRepository struct {
|
||||
chapters *mgo.Collection
|
||||
comments *mgo.Collection
|
||||
}
|
||||
|
||||
func newChapterRepository(db *mgo.Database) (repositories.ChapterRepository, error) {
|
||||
collection := db.C("story.chapters")
|
||||
|
||||
err := collection.EnsureIndexKey("storyId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("createdDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapterRepository{
|
||||
chapters: collection,
|
||||
comments: db.C("story.comments"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Find(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
chapter := new(models.Chapter)
|
||||
err := r.chapters.FindId(id).One(chapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error) {
|
||||
query := bson.M{}
|
||||
if filter.StoryID != nil {
|
||||
query["storyId"] = *filter.StoryID
|
||||
}
|
||||
|
||||
chapters := make([]*models.Chapter, 0, 32)
|
||||
err := r.chapters.Find(query).Sort("createdDate").Limit(filter.Limit).All(&chapters)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return chapters, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chapters, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error) {
|
||||
chapter.ID = generate.StoryID()
|
||||
|
||||
err := r.chapters.Insert(chapter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Chapter, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Title != nil {
|
||||
updateBson["title"] = *update.Title
|
||||
chapter.Title = *update.Title
|
||||
}
|
||||
if update.Source != nil {
|
||||
updateBson["source"] = *update.Source
|
||||
chapter.Source = *update.Source
|
||||
}
|
||||
if update.FictionalDate != nil {
|
||||
updateBson["fictionalDate"] = *update.FictionalDate
|
||||
chapter.FictionalDate = *update.FictionalDate
|
||||
}
|
||||
if update.CommentMode != nil {
|
||||
updateBson["commentMode"] = *update.CommentMode
|
||||
chapter.CommentMode = *update.CommentMode
|
||||
}
|
||||
if update.CommentsLocked != nil {
|
||||
updateBson["commentsLocked"] = *update.CommentsLocked
|
||||
chapter.CommentsLocked = *update.CommentsLocked
|
||||
}
|
||||
|
||||
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Move(ctx context.Context, chapter models.Chapter, from, to models.Story) (*models.Chapter, error) {
|
||||
err := r.chapters.UpdateId(chapter.ID, bson.M{"$set": bson.M{"storyId": to.ID}})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapter.StoryID = to.ID
|
||||
|
||||
return &chapter, nil
|
||||
}
|
||||
|
||||
func (r *chapterRepository) Delete(ctx context.Context, chapter models.Chapter) error {
|
||||
err := r.chapters.RemoveId(chapter.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c, err := r.comments.RemoveAll(bson.M{"chapterId": chapter.ID})
|
||||
if err != nil {
|
||||
log.Println("Failed to remove comments:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Removed chapter %s (%d comments)", chapter.ID, c.Removed)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package mongodb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/internal/generate"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"log"
|
||||
)
|
||||
|
||||
type commentRepository struct {
|
||||
comments *mgo.Collection
|
||||
}
|
||||
|
||||
func newCommentRepository(db *mgo.Database) (repositories.CommentRepository, error) {
|
||||
collection := db.C("story.comments")
|
||||
|
||||
err := collection.EnsureIndexKey("chapterId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("author")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = collection.EnsureIndexKey("createdDate")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &commentRepository{
|
||||
comments: collection,
|
||||
}
|
||||
|
||||
go r.fixFieldTypo()
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Find(ctx context.Context, id string) (*models.Comment, error) {
|
||||
comment := new(models.Comment)
|
||||
err := r.comments.FindId(id).One(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error) {
|
||||
query := bson.M{}
|
||||
if filter.ChapterID != nil {
|
||||
query["chapterId"] = *filter.ChapterID
|
||||
}
|
||||
|
||||
comments := make([]*models.Comment, 0, 32)
|
||||
err := r.comments.Find(query).Sort("createdDate").Limit(filter.Limit).All(&comments)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Insert(ctx context.Context, comment models.Comment) (*models.Comment, error) {
|
||||
comment.ID = generate.CommentID()
|
||||
|
||||
err := r.comments.Insert(comment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error) {
|
||||
updateBson := bson.M{}
|
||||
if update.Subject != nil {
|
||||
updateBson["subject"] = *update.Subject
|
||||
comment.Subject = *update.Subject
|
||||
}
|
||||
if update.Source != nil {
|
||||
updateBson["source"] = *update.Source
|
||||
comment.Source = *update.Source
|
||||
}
|
||||
if update.FictionalDate != nil {
|
||||
updateBson["fictionalDate"] = *update.FictionalDate
|
||||
comment.FictionalDate = *update.FictionalDate
|
||||
}
|
||||
if update.CharacterID != nil {
|
||||
updateBson["characterId"] = *update.CharacterID
|
||||
comment.CharacterID = *update.CharacterID
|
||||
}
|
||||
if update.CharacterName != nil {
|
||||
updateBson["characterName"] = *update.CharacterName
|
||||
comment.CharacterName = *update.CharacterName
|
||||
}
|
||||
|
||||
err := r.comments.UpdateId(comment.ID, bson.M{"$set": updateBson})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
}
|
||||
|
||||
func (r *commentRepository) Delete(ctx context.Context, comment models.Comment) error {
|
||||
return r.comments.RemoveId(comment.ID)
|
||||
}
|
||||
|
||||
func (r *commentRepository) fixFieldTypo() {
|
||||
c, err := r.comments.UpdateAll(bson.M{
|
||||
"editeddDate": bson.M{"$ne": nil},
|
||||
}, bson.M{
|
||||
"$rename": bson.M{"editeddDate": "editedDate"},
|
||||
})
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Failed to run name typo fix:", err)
|
||||
return
|
||||
}
|
||||
if c.Updated > 0 {
|
||||
log.Println("Fixed editeddDate field name typo in", c.Updated, "comments")
|
||||
}
|
||||
}
|
||||
+29
-5
@@ -21,7 +21,9 @@ type MongoDB struct {
|
||||
logs *logRepository
|
||||
posts *postRepository
|
||||
files *fileRepository
|
||||
story repositories.StoryRepository
|
||||
stories repositories.StoryRepository
|
||||
chapters repositories.ChapterRepository
|
||||
comments repositories.CommentRepository
|
||||
}
|
||||
|
||||
func (m *MongoDB) Changes() repositories.ChangeRepository {
|
||||
@@ -52,8 +54,16 @@ func (m *MongoDB) Files() repositories.FileRepository {
|
||||
return m.files
|
||||
}
|
||||
|
||||
func (m *MongoDB) Story() repositories.StoryRepository {
|
||||
return m.story
|
||||
func (m *MongoDB) Stories() repositories.StoryRepository {
|
||||
return m.stories
|
||||
}
|
||||
|
||||
func (m *MongoDB) Chapters() repositories.ChapterRepository {
|
||||
return m.chapters
|
||||
}
|
||||
|
||||
func (m *MongoDB) Comments() repositories.CommentRepository {
|
||||
return m.comments
|
||||
}
|
||||
|
||||
func (m *MongoDB) Close(ctx context.Context) error {
|
||||
@@ -119,7 +129,19 @@ func Init(cfg config.Database) (*MongoDB, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
story, err := newStoryRepository(db)
|
||||
stories, err := newStoryRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapters, err := newChapterRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments, err := newCommentRepository(db)
|
||||
if err != nil {
|
||||
session.Close()
|
||||
return nil, err
|
||||
@@ -134,7 +156,9 @@ func Init(cfg config.Database) (*MongoDB, error) {
|
||||
characters: characters,
|
||||
channels: channels,
|
||||
tags: newTagRepository(db),
|
||||
story: story,
|
||||
stories: stories,
|
||||
chapters: chapters,
|
||||
comments: comments,
|
||||
logs: logs,
|
||||
posts: posts,
|
||||
files: files,
|
||||
|
||||
@@ -72,7 +72,9 @@ func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) (
|
||||
query["tags"] = bson.M{"$all": filter.Tags}
|
||||
}
|
||||
if filter.Unlisted != nil {
|
||||
query["listed"] = *filter.Unlisted
|
||||
query["listed"] = !*filter.Unlisted
|
||||
} else {
|
||||
query["listed"] = true
|
||||
}
|
||||
if !filter.EarliestFictionalDate.IsZero() && !filter.LatestFictionalDate.IsZero() {
|
||||
query["fictionalDate"] = bson.M{
|
||||
@@ -90,7 +92,7 @@ func (r *storyRepository) List(ctx context.Context, filter models.StoryFilter) (
|
||||
}
|
||||
|
||||
stories := make([]*models.Story, 0, 32)
|
||||
err := r.stories.Find(query).Sort("-updatedDate ").Limit(filter.Limit).All(&stories)
|
||||
err := r.stories.Find(query).Sort("-updatedDate").Limit(filter.Limit).All(&stories)
|
||||
if err != nil {
|
||||
if err == mgo.ErrNotFound {
|
||||
return stories, nil
|
||||
@@ -195,7 +197,7 @@ func (r *storyRepository) Delete(ctx context.Context, story models.Story) error
|
||||
return nil
|
||||
}
|
||||
|
||||
c2, err := r.chapters.RemoveAll(bson.M{"comments": bson.M{"$in": chapterIds}})
|
||||
c2, err := r.comments.RemoveAll(bson.M{"chapterId": bson.M{"$in": chapterIds}})
|
||||
if err != nil {
|
||||
log.Println("Failed to remove comments:", err)
|
||||
return nil
|
||||
|
||||
@@ -33,6 +33,7 @@ require (
|
||||
golang.org/x/net v0.0.0-20190514140710-3ec191127204 // indirect
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/text v0.3.0 // indirect
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd
|
||||
google.golang.org/appengine v1.1.0 // indirect
|
||||
gopkg.in/ini.v1 v1.42.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"git.aiterp.net/rpdata/api/graph2/graphcore"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/files"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func complexity() (cr graphcore.ComplexityRoot) {
|
||||
@@ -40,7 +39,7 @@ func complexity() (cr graphcore.ComplexityRoot) {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Logs = func(childComplexity int, filter *models.LogFilter) int {
|
||||
if filter != nil && filter.Open != nil && *filter.Open == true {
|
||||
if filter != nil && ((filter.Open != nil && *filter.Open == true) || (filter.Limit <= 10)) {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
@@ -58,7 +57,11 @@ func complexity() (cr graphcore.ComplexityRoot) {
|
||||
cr.Query.Story = func(childComplexity int, id string) int {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
cr.Query.Stories = func(childComplexity int, filter *stories.Filter) int {
|
||||
cr.Query.Stories = func(childComplexity int, filter *models.StoryFilter) int {
|
||||
if filter != nil && filter.Limit <= 10 {
|
||||
return childComplexity + findComplexity
|
||||
}
|
||||
|
||||
return childComplexity + listComplexity
|
||||
}
|
||||
cr.Query.File = func(childComplexity int, id string) int {
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ models:
|
||||
StoryCategory:
|
||||
model: git.aiterp.net/rpdata/api/models.StoryCategory
|
||||
StoriesFilter:
|
||||
model: git.aiterp.net/rpdata/api/models/stories.Filter
|
||||
model: git.aiterp.net/rpdata/api/models.StoryFilter
|
||||
File:
|
||||
model: git.aiterp.net/rpdata/api/models.File
|
||||
FilesFilter:
|
||||
|
||||
+3
-3
@@ -39,15 +39,15 @@ func (r *rootResolver) Log() graphcore.LogResolver {
|
||||
}
|
||||
|
||||
func (r *rootResolver) Comment() graphcore.CommentResolver {
|
||||
return &types.CommentResolver
|
||||
return types.CommentResolver(r.s)
|
||||
}
|
||||
|
||||
func (r *rootResolver) Chapter() graphcore.ChapterResolver {
|
||||
return &types.ChapterResolver
|
||||
return types.ChapterResolver(r.s)
|
||||
}
|
||||
|
||||
func (r *rootResolver) Story() graphcore.StoryResolver {
|
||||
return &types.StoryResolver
|
||||
return types.StoryResolver(r.s)
|
||||
}
|
||||
|
||||
func (r *rootResolver) Change() graphcore.ChangeResolver {
|
||||
|
||||
+19
-125
@@ -2,54 +2,24 @@ package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/graphcore"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *queryResolver) Chapter(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chapter, nil
|
||||
return r.s.Stories.FindChapter(ctx, id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddChapter(ctx context.Context, input graphcore.ChapterAddInput) (*models.Chapter, error) {
|
||||
story, err := stories.FindID(input.StoryID)
|
||||
story, err := r.s.Stories.FindStory(ctx, input.StoryID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.add") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("story.add") {
|
||||
return nil, errors.New("False pretender")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
}
|
||||
|
||||
if !story.Open && story.Author != author {
|
||||
return nil, errors.New("Story is not open")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commentMode := models.ChapterCommentModeDisabled
|
||||
@@ -57,121 +27,45 @@ func (r *mutationResolver) AddChapter(ctx context.Context, input graphcore.Chapt
|
||||
commentMode = *input.CommentMode
|
||||
}
|
||||
|
||||
chapter, err := chapters.Add(story, input.Title, author, input.Source, time.Now(), input.FictionalDate, commentMode)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to create chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Chapter", "add", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
|
||||
return &chapter, nil
|
||||
return r.s.Stories.CreateChapter(ctx, *story, input.Title, input.Source, input.Author, time.Now(), input.FictionalDate, commentMode)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MoveChapter(ctx context.Context, input graphcore.ChapterMoveInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.move") {
|
||||
return nil, errors.New("You are not allowed to move this chapter")
|
||||
}
|
||||
|
||||
target, err := stories.FindID(input.StoryID)
|
||||
from, err := r.s.Stories.FindStory(ctx, chapter.StoryID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Target story not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !target.Open && !token.PermittedUser(target.Author, "member", "chapter.move") {
|
||||
return nil, errors.New("You are not permitted to move chapters to this story")
|
||||
}
|
||||
|
||||
oldStoryID := chapter.StoryID
|
||||
chapter, err = chapters.Move(chapter, target)
|
||||
to, err := r.s.Stories.FindStory(ctx, input.StoryID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to move chapter: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
oldStory, err := stories.FindID(oldStoryID)
|
||||
if err != nil {
|
||||
oldStory.ID = oldStoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "move-out", chapter.Author, oldStory.Listed, changekeys.Many(oldStory, chapter), chapter)
|
||||
changes.Submit("Chapter", "move-in", token.UserID, story.Listed, changekeys.Listed(story, chapter), story, chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
return r.s.Stories.MoveChapter(ctx, chapter, *from, *to)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditChapter(ctx context.Context, input graphcore.ChapterEditInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.edit") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
chapter, err = chapters.Edit(chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to edit chapter: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "edit", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
return r.s.Stories.EditChapter(ctx, chapter, input.Title, input.Source, input.FictionalDate, input.CommentMode, input.CommentsLocked)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveChapter(ctx context.Context, input graphcore.ChapterRemoveInput) (*models.Chapter, error) {
|
||||
chapter, err := chapters.FindID(input.ID)
|
||||
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.remove") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
chapter, err = chapters.Remove(chapter)
|
||||
err = r.s.Stories.RemoveChapter(ctx, chapter)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to remove chapter: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = comments.RemoveChapter(chapter)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter was removed, but comment removal failed: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
story.ID = chapter.StoryID
|
||||
}
|
||||
|
||||
changes.Submit("Chapter", "remove", token.UserID, story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
}()
|
||||
|
||||
return &chapter, nil
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
+18
-129
@@ -2,64 +2,24 @@ package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/graphcore"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/characters"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
// Queries
|
||||
|
||||
func (r *queryResolver) Comment(ctx context.Context, id string) (*models.Comment, error) {
|
||||
comment, err := comments.Find(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &comment, nil
|
||||
return r.s.Stories.FindComment(ctx, id)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddComment(ctx context.Context, input graphcore.CommentAddInput) (*models.Comment, error) {
|
||||
chapter, err := chapters.FindID(input.ChapterID)
|
||||
chapter, err := r.s.Stories.FindChapter(ctx, input.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Chapter not found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Permitted("member", "story.edit") {
|
||||
return nil, errors.New("Unauthorized")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
var characterPtr *models.Character
|
||||
if input.CharacterID != nil {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return nil, errors.New("That is not your character")
|
||||
}
|
||||
|
||||
characterPtr = &character
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subject := ""
|
||||
@@ -67,109 +27,38 @@ func (r *mutationResolver) AddComment(ctx context.Context, input graphcore.Comme
|
||||
subject = *input.Subject
|
||||
}
|
||||
|
||||
comment, err := comments.Add(chapter, subject, token.UserID, input.Source, input.CharacterName, characterPtr, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add comment: " + err.Error())
|
||||
fictionalDate := time.Time{}
|
||||
if input.FictionalDate != nil {
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "add", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return &comment, nil
|
||||
return r.s.Stories.CreateComment(ctx, *chapter, subject, "", input.Source, input.CharacterName, input.CharacterID, time.Now(), fictionalDate)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditComment(ctx context.Context, input graphcore.CommentEditInput) (*models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
comment, err := r.s.Stories.FindComment(ctx, input.CommentID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You cannot edit this comment")
|
||||
fictionalDate := input.FictionalDate
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
|
||||
fictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment's chapter not found")
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate == true {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
if input.CharacterID != nil && *input.CharacterID != "" {
|
||||
character, err := characters.FindID(*input.CharacterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Character not found")
|
||||
} else if character.Author != token.UserID {
|
||||
return nil, errors.New("That is not your character")
|
||||
}
|
||||
}
|
||||
|
||||
comment, err = comments.Edit(comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Could not post comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "edit", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return &comment, nil
|
||||
return r.s.Stories.EditComment(ctx, comment, input.Source, input.CharacterName, input.CharacterID, input.Subject, fictionalDate)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveComment(ctx context.Context, input graphcore.CommentRemoveInput) (*models.Comment, error) {
|
||||
comment, err := comments.Find(input.CommentID)
|
||||
comment, err := r.s.Stories.FindComment(ctx, input.CommentID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.PermittedUser(comment.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You cannot remove this comment")
|
||||
}
|
||||
|
||||
chapter, err := chapters.FindID(comment.ChapterID)
|
||||
err = r.s.Stories.RemoveComment(ctx, comment)
|
||||
if err != nil {
|
||||
return nil, errors.New("Comment's chapter not found")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("Comments are disabled or locked")
|
||||
}
|
||||
|
||||
err = comments.Remove(comment)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to remove comment: " + err.Error())
|
||||
}
|
||||
|
||||
go func() {
|
||||
story, err := stories.FindID(chapter.StoryID)
|
||||
if err != nil {
|
||||
log.Println("WARNING: Couldn't log comment change:", err)
|
||||
return
|
||||
}
|
||||
|
||||
changes.Submit("Comment", "remove", token.UserID, true, changekeys.Many(comment, chapter, models.Story{ID: chapter.StoryID}), comment, chapter, story)
|
||||
}()
|
||||
|
||||
return &comment, nil
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
+41
-160
@@ -2,69 +2,33 @@ package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/graph2/graphcore"
|
||||
"git.aiterp.net/rpdata/api/internal/auth"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/changekeys"
|
||||
"git.aiterp.net/rpdata/api/models/changes"
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
"git.aiterp.net/rpdata/api/models/stories"
|
||||
)
|
||||
|
||||
func (r *queryResolver) Story(ctx context.Context, id string) (*models.Story, error) {
|
||||
story, err := stories.FindID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
return r.s.Stories.FindStory(ctx, id)
|
||||
}
|
||||
|
||||
func (r *queryResolver) Stories(ctx context.Context, filter *stories.Filter) ([]*models.Story, error) {
|
||||
if filter != nil {
|
||||
if filter.Unlisted != nil && *filter.Unlisted == true {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if !token.Authenticated() {
|
||||
return nil, errors.New("You are not permitted to view unlisted stories")
|
||||
}
|
||||
|
||||
if !token.Permitted("story.unlisted") {
|
||||
filter.Author = &token.UserID
|
||||
}
|
||||
}
|
||||
func (r *queryResolver) Stories(ctx context.Context, filter *models.StoryFilter) ([]*models.Story, error) {
|
||||
if filter == nil {
|
||||
filter = &models.StoryFilter{}
|
||||
}
|
||||
|
||||
stories, err := stories.List(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stories2 := make([]*models.Story, len(stories))
|
||||
for i := range stories {
|
||||
stories2[i] = &stories[i]
|
||||
}
|
||||
|
||||
return stories2, nil
|
||||
return r.s.Stories.ListStories(ctx, *filter)
|
||||
}
|
||||
|
||||
// Mutations
|
||||
|
||||
func (r *mutationResolver) AddStory(ctx context.Context, input graphcore.StoryAddInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil || !token.Permitted("member", "story.add") {
|
||||
return nil, errors.New("Permission denied")
|
||||
}
|
||||
listed := input.Listed != nil && *input.Listed
|
||||
open := input.Open != nil && *input.Open
|
||||
|
||||
author := token.UserID
|
||||
if input.Author != nil && *input.Author != author {
|
||||
if !token.Permitted("story.add") {
|
||||
return nil, errors.New("You are not permitted to add a story in another author's name")
|
||||
}
|
||||
|
||||
author = *input.Author
|
||||
tags := make([]models.Tag, len(input.Tags))
|
||||
for i, tag := range input.Tags {
|
||||
tags[i] = *tag
|
||||
}
|
||||
|
||||
fictionalDate := time.Time{}
|
||||
@@ -72,129 +36,46 @@ func (r *mutationResolver) AddStory(ctx context.Context, input graphcore.StoryAd
|
||||
fictionalDate = *input.FictionalDate
|
||||
}
|
||||
|
||||
listed := input.Listed != nil && *input.Listed
|
||||
open := input.Open != nil && *input.Open
|
||||
|
||||
tags := make([]models.Tag, len(input.Tags))
|
||||
for i := range input.Tags {
|
||||
tags[i] = *input.Tags[i]
|
||||
}
|
||||
|
||||
story, err := stories.Add(input.Name, author, input.Category, listed, open, tags, time.Now(), fictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "add", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return &story, nil
|
||||
return r.s.Stories.CreateStory(ctx, input.Name, input.Author, input.Category, listed, open, tags, time.Now(), fictionalDate)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddStoryTag(ctx context.Context, input graphcore.StoryTagAddInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if story.Open {
|
||||
if !token.Permitted("member") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
} else {
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
}
|
||||
|
||||
story, err = stories.AddTag(story, *input.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "tag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input graphcore.StoryTagRemoveInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if story.Open {
|
||||
if !token.Permitted("member") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
} else {
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You are not permitted to edit this story")
|
||||
}
|
||||
}
|
||||
|
||||
story, err = stories.RemoveTag(story, *input.Tag)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "untag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input graphcore.StoryEditInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.edit") {
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
|
||||
input.FictionalDate = &time.Time{}
|
||||
}
|
||||
|
||||
story, err = stories.Edit(story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to add story: " + err.Error())
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "edit", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input graphcore.StoryRemoveInput) (*models.Story, error) {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
|
||||
story, err := stories.FindID(input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Story not found")
|
||||
}
|
||||
|
||||
if !token.PermittedUser(story.Author, "member", "story.remove") {
|
||||
return nil, errors.New("You are not permitted to remove this story")
|
||||
}
|
||||
|
||||
story, err = stories.Remove(story)
|
||||
story, err := r.s.Stories.FindStory(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = chapters.RemoveStory(story)
|
||||
return r.s.Stories.AddStoryTag(ctx, *story, *input.Tag)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input graphcore.StoryTagRemoveInput) (*models.Story, error) {
|
||||
story, err := r.s.Stories.FindStory(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to remove chapters, but story is removed: " + err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go changes.Submit("Story", "remove", token.UserID, story.Listed, changekeys.Listed(story), story)
|
||||
return r.s.Stories.RemoveStoryTag(ctx, *story, *input.Tag)
|
||||
}
|
||||
|
||||
return &story, nil
|
||||
func (r *mutationResolver) EditStory(ctx context.Context, input graphcore.StoryEditInput) (*models.Story, error) {
|
||||
story, err := r.s.Stories.FindStory(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.s.Stories.EditStory(ctx, story, input.Name, input.Category, input.Listed, input.Open, input.FictionalDate)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveStory(ctx context.Context, input graphcore.StoryRemoveInput) (*models.Story, error) {
|
||||
story, err := r.s.Stories.FindStory(ctx, input.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.s.Stories.RemoveStory(ctx, story)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return story, err
|
||||
}
|
||||
|
||||
+11
-26
@@ -2,14 +2,15 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/services"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
"git.aiterp.net/rpdata/api/models/comments"
|
||||
)
|
||||
|
||||
type chapterResolver struct{}
|
||||
type chapterResolver struct {
|
||||
stories *services.StoryService
|
||||
}
|
||||
|
||||
func (r *chapterResolver) FictionalDate(ctx context.Context, chapter *models.Chapter) (*time.Time, error) {
|
||||
if chapter.FictionalDate.IsZero() {
|
||||
@@ -20,31 +21,15 @@ func (r *chapterResolver) FictionalDate(ctx context.Context, chapter *models.Cha
|
||||
}
|
||||
|
||||
func (r *chapterResolver) Comments(ctx context.Context, chapter *models.Chapter, limit *int) ([]*models.Comment, error) {
|
||||
limitValue := 0
|
||||
if limit != nil {
|
||||
if *limit < 0 {
|
||||
return nil, errors.New("Limit cannot be negative")
|
||||
}
|
||||
|
||||
limitValue = *limit
|
||||
if limit == nil {
|
||||
limit = new(int)
|
||||
*limit = 0
|
||||
}
|
||||
|
||||
if !chapter.CommentMode.IsEnabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
comments, err := comments.ListChapterID(chapter.ID, limitValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments2 := make([]*models.Comment, len(comments))
|
||||
for i := range comments {
|
||||
comments2[i] = &comments[i]
|
||||
}
|
||||
|
||||
return comments2, nil
|
||||
return r.stories.ListComments(ctx, *chapter, *limit)
|
||||
}
|
||||
|
||||
// ChapterResolver is a resolver
|
||||
var ChapterResolver chapterResolver
|
||||
func ChapterResolver(s *services.Bundle) *chapterResolver {
|
||||
return &chapterResolver{stories: s.Stories}
|
||||
}
|
||||
|
||||
+15
-11
@@ -2,31 +2,33 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.aiterp.net/rpdata/api/repositories"
|
||||
"git.aiterp.net/rpdata/api/services"
|
||||
"github.com/globalsign/mgo"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/internal/loader"
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type commentResolver struct{}
|
||||
type commentResolver struct {
|
||||
characters *services.CharacterService
|
||||
}
|
||||
|
||||
func (r *commentResolver) Character(ctx context.Context, obj *models.Comment) (*models.Character, error) {
|
||||
if obj.CharacterID == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
loader := loader.FromContext(ctx)
|
||||
if loader == nil {
|
||||
return nil, errors.New("no loader")
|
||||
}
|
||||
|
||||
character, err := loader.Character("id", obj.CharacterID)
|
||||
character, err := r.characters.Find(ctx, obj.CharacterID)
|
||||
if err != nil {
|
||||
if err == repositories.ErrNotFound || err == mgo.ErrNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &character, nil
|
||||
return character, nil
|
||||
}
|
||||
|
||||
func (r *commentResolver) FictionalDate(ctx context.Context, obj *models.Comment) (*time.Time, error) {
|
||||
@@ -38,4 +40,6 @@ func (r *commentResolver) FictionalDate(ctx context.Context, obj *models.Comment
|
||||
}
|
||||
|
||||
// CommentResolver is a resolver
|
||||
var CommentResolver commentResolver
|
||||
func CommentResolver(s *services.Bundle) *commentResolver {
|
||||
return &commentResolver{characters: s.Characters}
|
||||
}
|
||||
|
||||
+8
-15
@@ -2,14 +2,15 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.aiterp.net/rpdata/api/services"
|
||||
"time"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models/chapters"
|
||||
|
||||
"git.aiterp.net/rpdata/api/models"
|
||||
)
|
||||
|
||||
type storyResolver struct{}
|
||||
type storyResolver struct {
|
||||
stories *services.StoryService
|
||||
}
|
||||
|
||||
func (r *storyResolver) FictionalDate(ctx context.Context, story *models.Story) (*time.Time, error) {
|
||||
if story.FictionalDate.IsZero() {
|
||||
@@ -20,18 +21,10 @@ func (r *storyResolver) FictionalDate(ctx context.Context, story *models.Story)
|
||||
}
|
||||
|
||||
func (r *storyResolver) Chapters(ctx context.Context, story *models.Story) ([]*models.Chapter, error) {
|
||||
chapters, err := chapters.ListStoryID(story.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapters2 := make([]*models.Chapter, len(chapters))
|
||||
for i := range chapters {
|
||||
chapters2[i] = &chapters[i]
|
||||
}
|
||||
|
||||
return chapters2, nil
|
||||
return r.stories.ListChapters(ctx, *story)
|
||||
}
|
||||
|
||||
// StoryResolver is a resolver
|
||||
var StoryResolver storyResolver
|
||||
func StoryResolver(s *services.Bundle) *storyResolver {
|
||||
return &storyResolver{stories: s.Stories}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,12 @@ func CheckPermission(ctx context.Context, op string, obj interface{}) error {
|
||||
return ErrUnauthenticated
|
||||
}
|
||||
|
||||
if reflect.TypeOf(obj).Kind() != reflect.Ptr {
|
||||
return CheckPermission(ctx, op, &obj)
|
||||
if v := reflect.ValueOf(obj); v.Kind() == reflect.Struct {
|
||||
ptr := reflect.PtrTo(v.Type())
|
||||
ptrValue := reflect.New(ptr.Elem())
|
||||
ptrValue.Elem().Set(v)
|
||||
|
||||
obj = ptrValue.Interface()
|
||||
}
|
||||
|
||||
var authorized = false
|
||||
|
||||
+6
-2
@@ -27,8 +27,12 @@ type Change struct {
|
||||
|
||||
// AddObject adds the model into the appropriate array.
|
||||
func (change *Change) AddObject(object interface{}) bool {
|
||||
if v := reflect.ValueOf(object); v.Kind() != reflect.Ptr && v.Kind() != reflect.Slice {
|
||||
return change.AddObject(v.Addr().Interface())
|
||||
if v := reflect.ValueOf(object); v.Kind() == reflect.Struct {
|
||||
ptr := reflect.PtrTo(v.Type())
|
||||
ptrValue := reflect.New(ptr.Elem())
|
||||
ptrValue.Elem().Set(v)
|
||||
|
||||
object = ptrValue.Interface()
|
||||
}
|
||||
|
||||
switch object := object.(type) {
|
||||
|
||||
@@ -9,6 +9,7 @@ type ChapterRepository interface {
|
||||
Find(ctx context.Context, id string) (*models.Chapter, error)
|
||||
List(ctx context.Context, filter models.ChapterFilter) ([]*models.Chapter, error)
|
||||
Insert(ctx context.Context, chapter models.Chapter) (*models.Chapter, error)
|
||||
Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Story, error)
|
||||
Update(ctx context.Context, chapter models.Chapter, update models.ChapterUpdate) (*models.Chapter, error)
|
||||
Move(ctx context.Context, chapter models.Chapter, from, to models.Story) (*models.Chapter, error)
|
||||
Delete(ctx context.Context, chapter models.Chapter) error
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@ type CommentRepository interface {
|
||||
Find(ctx context.Context, id string) (*models.Comment, error)
|
||||
List(ctx context.Context, filter models.CommentFilter) ([]*models.Comment, error)
|
||||
Insert(ctx context.Context, comment models.Comment) (*models.Comment, error)
|
||||
Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Story, error)
|
||||
Update(ctx context.Context, comment models.Comment, update models.CommentUpdate) (*models.Comment, error)
|
||||
Delete(ctx context.Context, comment models.Comment) error
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ type Bundle struct {
|
||||
Changes *ChangeService
|
||||
Logs *LogService
|
||||
Channels *ChannelService
|
||||
Stories *StoryService
|
||||
}
|
||||
|
||||
// NewBundle creates a new bundle.
|
||||
@@ -38,6 +39,13 @@ func NewBundle(db database.Database) *Bundle {
|
||||
channelService: bundle.Channels,
|
||||
characterService: bundle.Characters,
|
||||
}
|
||||
bundle.Stories = &StoryService{
|
||||
stories: db.Stories(),
|
||||
chapters: db.Chapters(),
|
||||
comments: db.Comments(),
|
||||
changeService: bundle.Changes,
|
||||
characterService: bundle.Characters,
|
||||
}
|
||||
|
||||
return bundle
|
||||
}
|
||||
|
||||
+281
-15
@@ -13,16 +13,25 @@ import (
|
||||
|
||||
// StoryService is a service governing all operations on stories and child objects.
|
||||
type StoryService struct {
|
||||
stories repositories.StoryRepository
|
||||
chapters repositories.ChapterRepository
|
||||
comments repositories.CommentRepository
|
||||
changeService *ChangeService
|
||||
stories repositories.StoryRepository
|
||||
chapters repositories.ChapterRepository
|
||||
comments repositories.CommentRepository
|
||||
changeService *ChangeService
|
||||
characterService *CharacterService
|
||||
}
|
||||
|
||||
func (s *StoryService) FindStory(ctx context.Context, id string) (*models.Story, error) {
|
||||
return s.stories.Find(ctx, id)
|
||||
}
|
||||
|
||||
func (s *StoryService) FindChapter(ctx context.Context, id string) (*models.Chapter, error) {
|
||||
return s.chapters.Find(ctx, id)
|
||||
}
|
||||
|
||||
func (s *StoryService) FindComment(ctx context.Context, id string) (*models.Comment, error) {
|
||||
return s.comments.Find(ctx, id)
|
||||
}
|
||||
|
||||
func (s *StoryService) ListStories(ctx context.Context, filter models.StoryFilter) ([]*models.Story, error) {
|
||||
return s.stories.List(ctx, filter)
|
||||
}
|
||||
@@ -35,10 +44,19 @@ func (s *StoryService) ListComments(ctx context.Context, chapter models.Chapter,
|
||||
return s.comments.List(ctx, models.CommentFilter{ChapterID: &chapter.ID, Limit: limit})
|
||||
}
|
||||
|
||||
func (s *StoryService) CreateStory(ctx context.Context, name, author string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
|
||||
func (s *StoryService) CreateStory(ctx context.Context, name string, author *string, category models.StoryCategory, listed, open bool, tags []models.Tag, createdDate, fictionalDate time.Time) (*models.Story, error) {
|
||||
if author == nil {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
}
|
||||
|
||||
author = &token.UserID
|
||||
}
|
||||
|
||||
story := &models.Story{
|
||||
Name: name,
|
||||
Author: author,
|
||||
Author: *author,
|
||||
Category: category,
|
||||
Listed: listed,
|
||||
Open: open,
|
||||
@@ -62,12 +80,21 @@ func (s *StoryService) CreateStory(ctx context.Context, name, author string, cat
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, title, author, source string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
|
||||
func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, title, source string, author *string, createdDate time.Time, fictionalDate *time.Time, commentMode models.ChapterCommentMode) (*models.Chapter, error) {
|
||||
if author == nil {
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if token == nil {
|
||||
return nil, auth.ErrUnauthenticated
|
||||
}
|
||||
|
||||
author = &token.UserID
|
||||
}
|
||||
|
||||
chapter := &models.Chapter{
|
||||
ID: generate.ChapterID(),
|
||||
StoryID: story.ID,
|
||||
Title: title,
|
||||
Author: author,
|
||||
Author: *author,
|
||||
Source: source,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
@@ -102,11 +129,13 @@ func (s *StoryService) CreateChapter(ctx context.Context, story models.Story, ti
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
// CreateComment adds a comment.
|
||||
func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter, subject, author, source, characterName string, character *models.Character, createdDate time.Time, fictionalDate time.Time) (*models.Comment, error) {
|
||||
characterID := ""
|
||||
if character != nil {
|
||||
characterID = character.ID
|
||||
func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter, subject, author, source, characterName string, characterID *string, createdDate time.Time, fictionalDate time.Time) (*models.Comment, error) {
|
||||
if characterID != nil {
|
||||
if err := s.permittedCharacter(ctx, "comment", *characterID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
characterID = new(string)
|
||||
}
|
||||
|
||||
if !chapter.CanComment() {
|
||||
@@ -127,7 +156,7 @@ func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter
|
||||
Subject: subject,
|
||||
Author: author,
|
||||
CharacterName: characterName,
|
||||
CharacterID: characterID,
|
||||
CharacterID: *characterID,
|
||||
FictionalDate: fictionalDate,
|
||||
CreatedDate: createdDate,
|
||||
EditedDate: createdDate,
|
||||
@@ -145,8 +174,245 @@ func (s *StoryService) CreateComment(ctx context.Context, chapter models.Chapter
|
||||
if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
|
||||
s.changeService.Submit(ctx, "Comment", "add", story.Listed, changekeys.Many(story, chapter, comment), comment)
|
||||
} else {
|
||||
s.changeService.Submit(ctx, "Comment", "add", false, changekeys.Many(chapter, comment), comment)
|
||||
s.changeService.Submit(ctx, "Comment", "add", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) EditStory(ctx context.Context, story *models.Story, name *string, category *models.StoryCategory, listed, open *bool, fictionalDate *time.Time) (*models.Story, error) {
|
||||
if story == nil {
|
||||
panic("StoryService.Edit called with nil story")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
story, err := s.stories.Update(ctx, *story, models.StoryUpdate{
|
||||
Name: name,
|
||||
Open: open,
|
||||
Listed: listed,
|
||||
Category: category,
|
||||
FictionalDate: fictionalDate,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.changeService.Submit(ctx, "Story", "edit", story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return story, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) AddStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
if err := auth.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := s.stories.AddTag(ctx, story, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
story.Tags = append(story.Tags, tag)
|
||||
|
||||
s.changeService.Submit(ctx, "Story", "tag", story.Listed, changekeys.Listed(story), story, tag)
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveStoryTag(ctx context.Context, story models.Story, tag models.Tag) (*models.Story, error) {
|
||||
if err := auth.CheckPermission(ctx, "edit", &story); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := s.stories.RemoveTag(ctx, story, tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i, tag2 := range story.Tags {
|
||||
if tag2 == tag {
|
||||
story.Tags = append(story.Tags[:i], story.Tags[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
s.changeService.Submit(ctx, "Story", "untag", story.Listed, changekeys.Listed(story), story, tag)
|
||||
|
||||
return &story, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) EditChapter(ctx context.Context, chapter *models.Chapter, title, source *string, fictionalDate *time.Time, commentMode *models.ChapterCommentMode, commentsLocked *bool) (*models.Chapter, error) {
|
||||
if chapter == nil {
|
||||
panic("StoryService.EditChapter called with nil chapter")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chapter, err := s.chapters.Update(ctx, *chapter, models.ChapterUpdate{
|
||||
Title: title,
|
||||
Source: source,
|
||||
FictionalDate: fictionalDate,
|
||||
CommentMode: commentMode,
|
||||
CommentsLocked: commentsLocked,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
|
||||
s.changeService.Submit(ctx, "Comment", "add", story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
} else {
|
||||
s.changeService.Submit(ctx, "Comment", "add", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter), chapter)
|
||||
}
|
||||
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) MoveChapter(ctx context.Context, chapter *models.Chapter, from, to models.Story) (*models.Chapter, error) {
|
||||
if err := auth.CheckPermission(ctx, "move", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if to.Open {
|
||||
if !auth.TokenFromContext(ctx).Permitted("member", "chapter.add") {
|
||||
return nil, auth.ErrUnauthorized
|
||||
}
|
||||
} else {
|
||||
if err := auth.CheckPermission(ctx, "add", chapter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := s.chapters.Move(ctx, *chapter, from, to)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.changeService.Submit(ctx, "Chapter", "move-out", from.Listed, changekeys.Listed(from), chapter)
|
||||
s.changeService.Submit(ctx, "Chapter", "move-in", to.Listed, changekeys.Listed(to), chapter)
|
||||
|
||||
return chapter, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) EditComment(ctx context.Context, comment *models.Comment, source, characterName, characterID, subject *string, fictionalDate *time.Time) (*models.Comment, error) {
|
||||
if comment == nil {
|
||||
panic("StoryService.EditChapter called with nil chapter")
|
||||
}
|
||||
|
||||
if err := auth.CheckPermission(ctx, "edit", comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if characterID != nil && *characterID != "" && *characterID != comment.CharacterID {
|
||||
if err := s.permittedCharacter(ctx, "comment", *characterID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
chapter, err := s.chapters.Find(ctx, comment.ChapterID)
|
||||
if err != nil {
|
||||
return nil, errors.New("could not find chapter")
|
||||
}
|
||||
if !chapter.CanComment() {
|
||||
return nil, errors.New("comments are locked or disabled")
|
||||
}
|
||||
|
||||
comment, err = s.comments.Update(ctx, *comment, models.CommentUpdate{
|
||||
Source: source,
|
||||
CharacterName: characterName,
|
||||
CharacterID: characterID,
|
||||
FictionalDate: fictionalDate,
|
||||
Subject: subject,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
|
||||
s.changeService.Submit(ctx, "Comment", "edit", story.Listed, changekeys.Many(story, chapter, comment), comment)
|
||||
} else {
|
||||
s.changeService.Submit(ctx, "Comment", "edit", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveStory(ctx context.Context, story *models.Story) error {
|
||||
if err := auth.CheckPermission(ctx, "add", story); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.stories.Delete(ctx, *story)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.changeService.Submit(ctx, "Story", "remove", story.Listed, changekeys.Listed(story), story)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveChapter(ctx context.Context, chapter *models.Chapter) error {
|
||||
if err := auth.CheckPermission(ctx, "remove", chapter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.chapters.Delete(ctx, *chapter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
|
||||
s.changeService.Submit(ctx, "Chapter", "remove", story.Listed, changekeys.Many(story, chapter), chapter)
|
||||
} else {
|
||||
s.changeService.Submit(ctx, "Chapter", "remove", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter), chapter)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StoryService) RemoveComment(ctx context.Context, comment *models.Comment) error {
|
||||
if err := auth.CheckPermission(ctx, "remove", comment); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chapter, err := s.chapters.Find(ctx, comment.ChapterID)
|
||||
if err != nil {
|
||||
return errors.New("could not find parent chapter")
|
||||
}
|
||||
if !chapter.CanComment() {
|
||||
return errors.New("comments are locked or disabled")
|
||||
}
|
||||
|
||||
err = s.comments.Delete(ctx, *comment)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if story, err := s.stories.Find(ctx, chapter.StoryID); err == nil {
|
||||
s.changeService.Submit(ctx, "Chapter", "remove", story.Listed, changekeys.Many(story, chapter, comment), comment)
|
||||
} else {
|
||||
s.changeService.Submit(ctx, "Chapter", "remove", false, changekeys.Many(models.Story{ID: chapter.StoryID}, chapter, comment), comment)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StoryService) permittedCharacter(ctx context.Context, permissionKind, characterID string) error {
|
||||
character, err := s.characterService.Find(ctx, characterID)
|
||||
if err != nil {
|
||||
return errors.New("character could not be found")
|
||||
}
|
||||
|
||||
token := auth.TokenFromContext(ctx)
|
||||
if character.Author != token.UserID && !token.Permitted(permissionKind+".edit") {
|
||||
return errors.New("you are not permitted to use others' character")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user