Browse Source

Finished refactor of story/chapter/comment related stuff.

thegreatrefactor
Gisle Aune 5 years ago
parent
commit
2eed815993
  1. 3
      database/database.go
  2. 2
      database/mongodb/changes.go
  3. 137
      database/mongodb/chapters.go
  4. 134
      database/mongodb/comments.go
  5. 34
      database/mongodb/db.go
  6. 8
      database/mongodb/stories.go
  7. 1
      go.mod
  8. 9
      graph2/complexity.go
  9. 2
      graph2/gqlgen.yml
  10. 6
      graph2/graph.go
  11. 144
      graph2/resolvers/chapter.go
  12. 147
      graph2/resolvers/comment.go
  13. 167
      graph2/resolvers/story.go
  14. 37
      graph2/types/chapter.go
  15. 26
      graph2/types/comment.go
  16. 23
      graph2/types/story.go
  17. 8
      internal/auth/permitted.go
  18. 8
      models/change.go
  19. 3
      repositories/chapter.go
  20. 2
      repositories/comment.go
  21. 8
      services/services.go
  22. 296
      services/stories.go

3
database/database.go

@ -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
}

2
database/mongodb/changes.go

@ -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

137
database/mongodb/chapters.go

@ -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
}

134
database/mongodb/comments.go

@ -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")
}
}

34
database/mongodb/db.go

@ -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,

8
database/mongodb/stories.go

@ -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

1
go.mod

@ -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

9
graph2/complexity.go

@ -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 {

2
graph2/gqlgen.yml

@ -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:

6
graph2/graph.go

@ -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 {

144
graph2/resolvers/chapter.go

@ -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")
}
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")
return nil, err
}
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")
}
if !target.Open && !token.PermittedUser(target.Author, "member", "chapter.move") {
return nil, errors.New("You are not permitted to move chapters to this story")
return nil, err
}
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)
if err != nil {
return nil, errors.New("Chapter not found")
}
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)
chapter, err := r.s.Stories.FindChapter(ctx, input.ID)
if err != nil {
return nil, errors.New("Failed to edit chapter: " + err.Error())
return nil, err
}
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")
}
token := auth.TokenFromContext(ctx)
if !token.Authenticated() || !token.PermittedUser(chapter.Author, "member", "chapter.remove") {
return nil, errors.New("Unauthorized")
}
chapter, err = chapters.Remove(chapter)
if err != nil {
return nil, errors.New("Failed to remove chapter: " + err.Error())
return nil, err
}
err = comments.RemoveChapter(chapter)
err = r.s.Stories.RemoveChapter(ctx, chapter)
if err != nil {
return nil, errors.New("Chapter was removed, but comment removal failed: " + err.Error())
return nil, err
}
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
}

147
graph2/resolvers/comment.go

@ -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")
}
token := auth.TokenFromContext(ctx)
if !token.PermittedUser(comment.Author, "member", "story.edit") {
return nil, errors.New("You cannot edit this comment")
}
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")
}
return nil, err
}
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())
fictionalDate := input.FictionalDate
if input.ClearFictionalDate != nil && *input.ClearFictionalDate {
fictionalDate = &time.Time{}
}
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)
if err != nil {
return nil, errors.New("Comment not found")
}
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)
comment, err := r.s.Stories.FindComment(ctx, input.CommentID)
if err != nil {
return nil, errors.New("Comment's chapter not found")
}
if !chapter.CanComment() {
return nil, errors.New("Comments are disabled or locked")
return nil, err
}
err = comments.Remove(comment)
err = r.s.Stories.RemoveComment(ctx, comment)
if err != nil {
return nil, errors.New("Failed to remove comment: " + err.Error())
return nil, err
}
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
}

167
graph2/resolvers/story.go

@ -2,199 +2,80 @@ 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
}
}
}
stories, err := stories.List(filter)
if err != nil {
return nil, err
func (r *queryResolver) Stories(ctx context.Context, filter *models.StoryFilter) ([]*models.Story, error) {
if filter == nil {
filter = &models.StoryFilter{}
}
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")
}
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
}
fictionalDate := time.Time{}
if input.FictionalDate != nil {
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]
for i, tag := range input.Tags {
tags[i] = *tag
}
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())
fictionalDate := time.Time{}
if input.FictionalDate != nil {
fictionalDate = *input.FictionalDate
}
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)
story, err := r.s.Stories.FindStory(ctx, 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())
return nil, err
}
go changes.Submit("Story", "tag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
return &story, nil
return r.s.Stories.AddStoryTag(ctx, *story, *input.Tag)
}
func (r *mutationResolver) RemoveStoryTag(ctx context.Context, input graphcore.StoryTagRemoveInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
story, err := r.s.Stories.FindStory(ctx, 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())
return nil, err
}
go changes.Submit("Story", "untag", token.UserID, story.Listed, changekeys.Listed(story), story, input.Tag)
return &story, nil
return r.s.Stories.RemoveStoryTag(ctx, *story, *input.Tag)
}
func (r *mutationResolver) EditStory(ctx context.Context, input graphcore.StoryEditInput) (*models.Story, error) {
token := auth.TokenFromContext(ctx)
story, err := stories.FindID(input.ID)
story, err := r.s.Stories.FindStory(ctx, 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())
return nil, err
}
go changes.Submit("Story", "edit", token.UserID, story.Listed, changekeys.Listed(story), story)
return &story, nil
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) {
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)
err = r.s.Stories.RemoveStory(ctx, story)
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 &story, nil
return story, err
}

37
graph2/types/chapter.go

@ -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 !chapter.CommentMode.IsEnabled() {
return nil, nil
if limit == nil {
limit = new(int)
*limit = 0
}
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}
}

26
graph2/types/comment.go

@ -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}
}

23
graph2/types/story.go

@ -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}
}

8
internal/auth/permitted.go

@ -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

8
models/change.go

@ -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) {

3
repositories/chapter.go

@ -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
}

2
repositories/comment.go

@ -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
}

8
services/services.go

@ -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
}

296
services/stories.go

@ -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
}
Loading…
Cancel
Save